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_linked_chunks(&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().as_deref(), 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().as_deref(), 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_linked_chunks(&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_linked_chunks().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(),
1441                    event_raclette.event_id().unwrap(),
1442                    event_morbier.event_id().unwrap(),
1443                    event_gruyere.event_id().unwrap(),
1444                    event_tome.event_id().unwrap(),
1445                    event_mont_dor.event_id().unwrap(),
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_linked_chunks().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
1654                .iter()
1655                .any(|(ev, pos)| ev.event_id().as_deref() == Some(edit_eid1) && pos.is_none())
1656        );
1657        assert!(
1658            relations
1659                .iter()
1660                .any(|(ev, pos)| ev.event_id().as_deref() == Some(reaction_eid1) && pos.is_none())
1661        );
1662
1663        // Finding relations with a filter only returns a subset.
1664        let relations = self
1665            .find_event_relations(room_id, eid1, Some(&[RelationType::Replacement]))
1666            .await
1667            .unwrap();
1668        assert_eq!(relations.len(), 1);
1669        assert_eq!(relations[0].0.event_id().as_deref(), Some(edit_eid1));
1670
1671        let relations = self
1672            .find_event_relations(
1673                room_id,
1674                eid1,
1675                Some(&[RelationType::Replacement, RelationType::Annotation]),
1676            )
1677            .await
1678            .unwrap();
1679        assert_eq!(relations.len(), 2);
1680        assert!(relations.iter().any(|r| r.0.event_id().as_deref() == Some(edit_eid1)));
1681        assert!(relations.iter().any(|r| r.0.event_id().as_deref() == Some(reaction_eid1)));
1682
1683        // We can't find relations using the wrong room.
1684        let relations = self
1685            .find_event_relations(another_room_id, eid1, Some(&[RelationType::Replacement]))
1686            .await
1687            .unwrap();
1688        assert!(relations.is_empty());
1689
1690        // But if an event exists in the linked chunk, we may have its position when
1691        // it's found as a relationship.
1692
1693        // Add reaction_e1 to the room's linked chunk.
1694        self.handle_linked_chunk_updates(
1695            LinkedChunkId::Room(room_id),
1696            vec![
1697                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1698                Update::PushItems { at: Position::new(CId::new(0), 0), items: vec![reaction_e1] },
1699            ],
1700        )
1701        .await
1702        .unwrap();
1703
1704        // When looking for aggregations to e1, we should have the position for
1705        // reaction_e1.
1706        let relations = self.find_event_relations(room_id, eid1, None).await.unwrap();
1707
1708        // The position is set for `reaction_eid1` now.
1709        assert!(relations.iter().any(|(ev, pos)| {
1710            ev.event_id().as_deref() == Some(reaction_eid1)
1711                && *pos == Some(Position::new(CId::new(0), 0))
1712        }));
1713
1714        // But it's still not set for the other related events.
1715        assert!(
1716            relations
1717                .iter()
1718                .any(|(ev, pos)| ev.event_id().as_deref() == Some(edit_eid1) && pos.is_none())
1719        );
1720    }
1721
1722    async fn test_find_event_relations_when_event_in_room_and_thread(&self) {
1723        let room_id = *DEFAULT_TEST_ROOM_ID;
1724        let thread_root = event_id!("$thread_root");
1725
1726        // Create an event that will inserted into both the room and thread linked
1727        // chunks.
1728        let event_id = event_id!("$event");
1729        let event = make_test_event_with_event_id(room_id, "event", Some(event_id));
1730
1731        // Create an event that will only be inserted into the thread in order to help
1732        // distinguish between the room and thread linked chunks.
1733        let extra_thread_event_id = event_id!("$extra_thread_event");
1734        let extra_thread_event = make_test_event_with_event_id(
1735            room_id,
1736            "extra thread event",
1737            Some(extra_thread_event_id),
1738        );
1739
1740        // Create a reaction that will only be inserted into the room
1741        let room_reaction_id = event_id!("$room_reaction");
1742        let room_reaction = EventFactory::new()
1743            .room(room_id)
1744            .sender(*ALICE)
1745            .reaction(event_id, "room")
1746            .event_id(room_reaction_id)
1747            .into_event();
1748
1749        // Create a reaction that will only be inserted into the thread
1750        let thread_reaction_id = event_id!("$thread_reaction");
1751        let thread_reaction = EventFactory::new()
1752            .room(room_id)
1753            .sender(*ALICE)
1754            .reaction(event_id, "thread")
1755            .event_id(thread_reaction_id)
1756            .into_event();
1757
1758        // Create a reaction that will be inserted into both the room and thread linked
1759        // chunks.
1760        let room_and_thread_reaction_id = event_id!("$room_and_thread_reaction");
1761        let room_and_thread_reaction = EventFactory::new()
1762            .room(room_id)
1763            .sender(*ALICE)
1764            .reaction(event_id, "room and thread")
1765            .event_id(room_and_thread_reaction_id)
1766            .into_event();
1767
1768        let room_linked_chunk_id = LinkedChunkId::Room(room_id);
1769        let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
1770
1771        // Insert the relevant events into the room's linked chunk.
1772        self.handle_linked_chunk_updates(
1773            room_linked_chunk_id,
1774            vec![
1775                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
1776                Update::PushItems {
1777                    at: Position::new(CId::new(1), 0),
1778                    items: vec![event.clone(), room_reaction, room_and_thread_reaction.clone()],
1779                },
1780            ],
1781        )
1782        .await
1783        .unwrap();
1784
1785        // Insert the relevant events into the thread's linked chunk.
1786        self.handle_linked_chunk_updates(
1787            thread_linked_chunk_id,
1788            vec![
1789                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
1790                Update::PushItems {
1791                    at: Position::new(CId::new(1), 0),
1792                    items: vec![
1793                        event.clone(),
1794                        extra_thread_event,
1795                        thread_reaction,
1796                        room_and_thread_reaction,
1797                    ],
1798                },
1799            ],
1800        )
1801        .await
1802        .unwrap();
1803
1804        // Verify that only related events from the room are returned
1805        assert_matches!(self.find_event_relations(room_id, event_id, None).await, Ok(relations) => {
1806            assert_eq!(relations.len(), 3);
1807            // Verify that room reaction is in the list and associated with its
1808            // position in the room linked chunk.
1809            let room_relation = relations
1810                .iter()
1811                .find(|relation| relation.0.event_id().unwrap() == room_reaction_id)
1812                .unwrap();
1813            assert_matches!(room_relation, (_, Some(position)) => {
1814                assert_eq!(*position, Position::new(CId::new(1), 1));
1815            });
1816
1817            // Verify that thread reaction is in the list and not associated with a
1818            // position, as all positions are provided for the room linked chunk.
1819            let thread_relation = relations
1820                .iter()
1821                .find(|relation| relation.0.event_id().unwrap() == thread_reaction_id)
1822                .unwrap();
1823            assert_matches!(thread_relation, (_, None));
1824
1825            // Verify that room and thread reaction is in the list and associated
1826            // with its position in the room linked chunk, not the thread linked chunk.
1827            let room_and_thread_relation = relations
1828                .iter()
1829                .find(|relation| relation.0.event_id().unwrap() == room_and_thread_reaction_id)
1830                .unwrap();
1831            assert_matches!(room_and_thread_relation, (_, Some(position)) => {
1832                assert_eq!(*position, Position::new(CId::new(1), 2));
1833            });
1834        });
1835    }
1836
1837    async fn test_get_room_events(&self) {
1838        let room_id = room_id!("!r0:matrix.org");
1839        let another_room_id = room_id!("!r1:matrix.org");
1840        let linked_chunk_id = LinkedChunkId::Room(room_id);
1841        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
1842        let event = |msg: &str| make_test_event(room_id, msg);
1843
1844        let event_comte = event("comté");
1845        let event_gruyere = event("gruyère");
1846        let event_stilton = event("stilton");
1847
1848        // Add one event in one room.
1849        self.handle_linked_chunk_updates(
1850            linked_chunk_id,
1851            vec![
1852                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1853                Update::PushItems {
1854                    at: Position::new(CId::new(0), 0),
1855                    items: vec![event_comte.clone(), event_gruyere.clone()],
1856                },
1857            ],
1858        )
1859        .await
1860        .unwrap();
1861
1862        // Add an event in a different room.
1863        self.handle_linked_chunk_updates(
1864            another_linked_chunk_id,
1865            vec![
1866                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1867                Update::PushItems {
1868                    at: Position::new(CId::new(0), 0),
1869                    items: vec![event_stilton.clone()],
1870                },
1871            ],
1872        )
1873        .await
1874        .unwrap();
1875
1876        // Now let's find the events.
1877        let events = self
1878            .get_room_events(room_id, None, None)
1879            .await
1880            .expect("failed to query for room events");
1881
1882        assert_eq!(events.len(), 2);
1883
1884        let got_ids: Vec<_> = events.into_iter().map(|ev| ev.event_id()).collect();
1885        let expected_ids = vec![event_comte.event_id(), event_gruyere.event_id()];
1886
1887        for expected in expected_ids {
1888            assert!(
1889                got_ids.contains(&expected),
1890                "Expected event {expected:?} not in got events: {got_ids:?}."
1891            );
1892        }
1893    }
1894
1895    async fn test_get_room_events_filtered(&self) {
1896        macro_rules! assert_expected_events {
1897            ($events:expr, [$($item:expr),* $(,)?]) => {{
1898                let got_ids: BTreeSet<_> = $events.into_iter().map(|ev| ev.event_id().unwrap()).collect();
1899                let expected_ids = BTreeSet::from([$($item.event_id().unwrap()),*]);
1900
1901                assert_eq!(got_ids, expected_ids);
1902            }};
1903        }
1904
1905        let room_id = room_id!("!r0:matrix.org");
1906        let linked_chunk_id = LinkedChunkId::Room(room_id);
1907        let another_room_id = room_id!("!r1:matrix.org");
1908        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
1909
1910        let event = |session_id: &str| make_encrypted_test_event(room_id, session_id);
1911
1912        let first_event = event("session_1");
1913        let second_event = event("session_2");
1914        let third_event = event("session_3");
1915        let fourth_event = make_test_event(room_id, "It's a secret to everybody");
1916
1917        // Add one event in one room.
1918        self.handle_linked_chunk_updates(
1919            linked_chunk_id,
1920            vec![
1921                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1922                Update::PushItems {
1923                    at: Position::new(CId::new(0), 0),
1924                    items: vec![first_event.clone(), second_event.clone(), fourth_event.clone()],
1925                },
1926            ],
1927        )
1928        .await
1929        .unwrap();
1930
1931        // Add an event in a different room.
1932        self.handle_linked_chunk_updates(
1933            another_linked_chunk_id,
1934            vec![
1935                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1936                Update::PushItems {
1937                    at: Position::new(CId::new(0), 0),
1938                    items: vec![third_event.clone()],
1939                },
1940            ],
1941        )
1942        .await
1943        .unwrap();
1944
1945        // Now let's find all the encrypted events of the first room.
1946        let events = self
1947            .get_room_events(room_id, Some("m.room.encrypted"), None)
1948            .await
1949            .expect("failed to query for room events");
1950
1951        assert_eq!(events.len(), 2);
1952        assert_expected_events!(events, [first_event, second_event]);
1953
1954        // Now let's find all the encrypted events which were encrypted using the first
1955        // session ID.
1956        let events = self
1957            .get_room_events(room_id, Some("m.room.encrypted"), Some("session_1"))
1958            .await
1959            .expect("failed to query for room events");
1960
1961        assert_eq!(events.len(), 1);
1962        assert_expected_events!(events, [first_event]);
1963    }
1964
1965    async fn test_get_room_events_with_event_in_room_and_thread(&self) {
1966        let room_id = *DEFAULT_TEST_ROOM_ID;
1967        let thread_root = event_id!("$thread_root");
1968
1969        // Create an event that will be only be inserted into the room
1970        let room_event_id = event_id!("$room_event");
1971        let room_event = make_test_event_with_event_id(room_id, "room event", Some(room_event_id));
1972
1973        // Create an event that will only be inserted into the thread. This may not be a
1974        // sensible operation in practice, as threads seem to always exist in a
1975        // room, but let's test it anyway.
1976        let thread_event_id = event_id!("$thread_event");
1977        let thread_event =
1978            make_test_event_with_event_id(room_id, "thread event", Some(thread_event_id));
1979
1980        // Create an event that will be inserted into both the room and thread linked
1981        // chunks.
1982        let room_and_thread_event_id = event_id!("$room_and_thread");
1983        let room_and_thread_event = make_test_event_with_event_id(
1984            room_id,
1985            "room and thread",
1986            Some(room_and_thread_event_id),
1987        );
1988
1989        let room_linked_chunk_id = LinkedChunkId::Room(room_id);
1990        let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
1991
1992        // Insert the relevant events into the room's linked chunk.
1993        self.handle_linked_chunk_updates(
1994            room_linked_chunk_id,
1995            vec![
1996                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
1997                Update::PushItems {
1998                    at: Position::new(CId::new(1), 0),
1999                    items: vec![room_event, room_and_thread_event.clone()],
2000                },
2001            ],
2002        )
2003        .await
2004        .unwrap();
2005
2006        // Insert the relevant events into the thread's linked chunk.
2007        self.handle_linked_chunk_updates(
2008            thread_linked_chunk_id,
2009            vec![
2010                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2011                Update::PushItems {
2012                    at: Position::new(CId::new(1), 0),
2013                    items: vec![thread_event, room_and_thread_event],
2014                },
2015            ],
2016        )
2017        .await
2018        .unwrap();
2019
2020        // Verify that all events can be retrieved and none are duplicated in the
2021        // returned list.
2022        let expected_event_ids =
2023            BTreeSet::from([room_event_id, thread_event_id, room_and_thread_event_id]);
2024        assert_matches!(self.get_room_events(room_id, None, None).await, Ok(events) => {
2025            assert_eq!(events.len(), 3);
2026            assert!(events.iter().all(|event| {
2027                expected_event_ids.contains(&event.event_id().unwrap().as_ref())
2028            }));
2029        });
2030    }
2031
2032    async fn test_save_event(&self) {
2033        let room_id = room_id!("!r0:matrix.org");
2034        let another_room_id = room_id!("!r1:matrix.org");
2035
2036        let event = |msg: &str| make_test_event(room_id, msg);
2037        let event_comte = event("comté");
2038        let event_gruyere = event("gruyère");
2039
2040        // Add one event in one room.
2041        self.save_event(room_id, event_comte.clone()).await.unwrap();
2042
2043        // Add another event in another room.
2044        self.save_event(another_room_id, event_gruyere.clone()).await.unwrap();
2045
2046        // Events can be found, when searched in their own rooms.
2047        let event = self
2048            .find_event(room_id, event_comte.event_id().unwrap().as_ref())
2049            .await
2050            .expect("failed to query for finding an event")
2051            .expect("failed to find an event");
2052        assert_eq!(event.event_id(), event_comte.event_id());
2053
2054        let event = self
2055            .find_event(another_room_id, event_gruyere.event_id().unwrap().as_ref())
2056            .await
2057            .expect("failed to query for finding an event")
2058            .expect("failed to find an event");
2059        assert_eq!(event.event_id(), event_gruyere.event_id());
2060
2061        // But they won't be returned when searching in the wrong room.
2062        assert!(
2063            self.find_event(another_room_id, event_comte.event_id().unwrap().as_ref())
2064                .await
2065                .expect("failed to query for finding an event")
2066                .is_none()
2067        );
2068        assert!(
2069            self.find_event(room_id, event_gruyere.event_id().unwrap().as_ref())
2070                .await
2071                .expect("failed to query for finding an event")
2072                .is_none()
2073        );
2074    }
2075
2076    async fn test_save_event_updates_event_in_room_and_thread(&self) {
2077        let room_id = *DEFAULT_TEST_ROOM_ID;
2078        let thread_root = event_id!("$thread_root");
2079
2080        // Create an event that will be inserted into both the room and thread linked
2081        // chunks.
2082        let event_id = event_id!("$event");
2083        let event = make_test_event_with_event_id(room_id, "event", Some(event_id));
2084
2085        let room_linked_chunk_id = LinkedChunkId::Room(room_id);
2086        let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
2087
2088        // Insert the relevant events into the room's linked chunk.
2089        self.handle_linked_chunk_updates(
2090            room_linked_chunk_id,
2091            vec![
2092                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2093                Update::PushItems { at: Position::new(CId::new(1), 0), items: vec![event.clone()] },
2094            ],
2095        )
2096        .await
2097        .unwrap();
2098
2099        // Insert the relevant events into the thread's linked chunk.
2100        self.handle_linked_chunk_updates(
2101            thread_linked_chunk_id,
2102            vec![
2103                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2104                Update::PushItems { at: Position::new(CId::new(1), 0), items: vec![event.clone()] },
2105            ],
2106        )
2107        .await
2108        .unwrap();
2109
2110        // Save updated version of original event, which should replace the content of
2111        // the existing event
2112        let updated_content = "updated content";
2113        let updated = make_test_event_with_event_id(room_id, updated_content, Some(event_id));
2114        self.save_event(room_id, updated).await.unwrap();
2115
2116        // Load all chunks from both room and thread
2117        let room_chunks = self.load_all_chunks(room_linked_chunk_id).await.unwrap();
2118        let thread_chunks = self.load_all_chunks(thread_linked_chunk_id).await.unwrap();
2119
2120        assert_eq!(room_chunks.len(), 1);
2121        assert_eq!(thread_chunks.len(), 1);
2122
2123        // Verify the event has been updated in both room and thread
2124        assert_matches!(&room_chunks[0].content, ChunkContent::Items(events) => {
2125            assert_eq!(events.len(), 1);
2126            assert_eq!(events[0].event_id().as_deref(), Some(event_id));
2127            check_test_event(&events[0], updated_content);
2128        });
2129        assert_matches!(&thread_chunks[0].content, ChunkContent::Items(events) => {
2130            assert_eq!(events.len(), 1);
2131            assert_eq!(events[0].event_id().as_deref(), Some(event_id));
2132            check_test_event(&events[0], updated_content);
2133        });
2134    }
2135
2136    async fn test_thread_vs_room_linked_chunk(&self) {
2137        let room_id = room_id!("!r0:matrix.org");
2138
2139        let event = |msg: &str| make_test_event(room_id, msg);
2140
2141        let thread1_ev = event("comté");
2142        let thread2_ev = event("gruyère");
2143        let thread2_ev2 = event("beaufort");
2144        let room_ev = event("brillat savarin triple crème");
2145
2146        let thread_root1 = event("thread1");
2147        let thread_root2 = event("thread2");
2148
2149        // Add one event in a thread linked chunk.
2150        self.handle_linked_chunk_updates(
2151            LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap().as_ref()),
2152            vec![
2153                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2154                Update::PushItems {
2155                    at: Position::new(CId::new(0), 0),
2156                    items: vec![thread1_ev.clone()],
2157                },
2158            ],
2159        )
2160        .await
2161        .unwrap();
2162
2163        // Add one event in another thread linked chunk (same room).
2164        self.handle_linked_chunk_updates(
2165            LinkedChunkId::Thread(room_id, thread_root2.event_id().unwrap().as_ref()),
2166            vec![
2167                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2168                Update::PushItems {
2169                    at: Position::new(CId::new(0), 0),
2170                    items: vec![thread2_ev.clone(), thread2_ev2.clone()],
2171                },
2172            ],
2173        )
2174        .await
2175        .unwrap();
2176
2177        // Add another event to the room linked chunk.
2178        self.handle_linked_chunk_updates(
2179            LinkedChunkId::Room(room_id),
2180            vec![
2181                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2182                Update::PushItems {
2183                    at: Position::new(CId::new(0), 0),
2184                    items: vec![room_ev.clone()],
2185                },
2186            ],
2187        )
2188        .await
2189        .unwrap();
2190
2191        // All the events can be found with `find_event()` for the room.
2192        self.find_event(room_id, thread2_ev.event_id().unwrap().as_ref())
2193            .await
2194            .expect("failed to query for finding an event")
2195            .expect("failed to find thread1_ev");
2196
2197        self.find_event(room_id, thread2_ev.event_id().unwrap().as_ref())
2198            .await
2199            .expect("failed to query for finding an event")
2200            .expect("failed to find thread2_ev");
2201
2202        self.find_event(room_id, thread2_ev2.event_id().unwrap().as_ref())
2203            .await
2204            .expect("failed to query for finding an event")
2205            .expect("failed to find thread2_ev2");
2206
2207        self.find_event(room_id, room_ev.event_id().unwrap().as_ref())
2208            .await
2209            .expect("failed to query for finding an event")
2210            .expect("failed to find room_ev");
2211
2212        // Finding duplicates operates based on the linked chunk id.
2213        let dups = self
2214            .filter_duplicated_events(
2215                LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap().as_ref()),
2216                vec![thread1_ev.event_id().unwrap(), room_ev.event_id().unwrap()],
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(
2226                room_id,
2227                thread_root2.event_id().unwrap().as_ref(),
2228            ))
2229            .await
2230            .unwrap();
2231        assert_eq!(all_chunks.len(), 1);
2232        assert_eq!(all_chunks[0].identifier, CId::new(0));
2233        assert_let!(ChunkContent::Items(observed_items) = all_chunks[0].content.clone());
2234        assert_eq!(observed_items.len(), 2);
2235        assert_eq!(observed_items[0].event_id(), thread2_ev.event_id());
2236        assert_eq!(observed_items[1].event_id(), thread2_ev2.event_id());
2237
2238        // Loading the metadata of all chunks operates based on the linked chunk
2239        // id.
2240        let metas = self
2241            .load_all_chunks_metadata(LinkedChunkId::Thread(
2242                room_id,
2243                thread_root2.event_id().unwrap().as_ref(),
2244            ))
2245            .await
2246            .unwrap();
2247        assert_eq!(metas.len(), 1);
2248        assert_eq!(metas[0].identifier, CId::new(0));
2249        assert_eq!(metas[0].num_items, 2);
2250
2251        // Loading the last chunk operates based on the linked chunk id.
2252        let (last_chunk, _chunk_identifier_generator) = self
2253            .load_last_chunk(LinkedChunkId::Thread(
2254                room_id,
2255                thread_root1.event_id().unwrap().as_ref(),
2256            ))
2257            .await
2258            .unwrap();
2259        let last_chunk = last_chunk.unwrap();
2260        assert_eq!(last_chunk.identifier, CId::new(0));
2261        assert_let!(ChunkContent::Items(observed_items) = last_chunk.content);
2262        assert_eq!(observed_items.len(), 1);
2263        assert_eq!(observed_items[0].event_id(), thread1_ev.event_id());
2264    }
2265}
2266
2267/// Macro building to allow your `EventCacheStore` implementation to run the
2268/// entire tests suite locally.
2269///
2270/// You need to provide a `async fn get_event_cache_store() ->
2271/// EventCacheStoreResult<impl EventCacheStore>` providing a fresh event cache
2272/// store on the same level you invoke the macro.
2273///
2274/// ## Usage Example:
2275/// ```no_run
2276/// # use matrix_sdk_base::event_cache::store::{
2277/// #    EventCacheStore,
2278/// #    MemoryStore as MyStore,
2279/// #    Result as EventCacheStoreResult,
2280/// # };
2281///
2282/// #[cfg(test)]
2283/// mod tests {
2284///     use super::{EventCacheStore, EventCacheStoreResult, MyStore};
2285///
2286///     async fn get_event_cache_store()
2287///     -> EventCacheStoreResult<impl EventCacheStore> {
2288///         Ok(MyStore::new())
2289///     }
2290///
2291///     event_cache_store_integration_tests!();
2292/// }
2293/// ```
2294#[allow(unused_macros, unused_extern_crates)]
2295#[macro_export]
2296macro_rules! event_cache_store_integration_tests {
2297    () => {
2298        mod event_cache_store_integration_tests {
2299            use matrix_sdk_test::async_test;
2300            use $crate::event_cache::store::{
2301                EventCacheStoreIntegrationTests, IntoEventCacheStore,
2302            };
2303
2304            use super::get_event_cache_store;
2305
2306            #[async_test]
2307            async fn test_handle_updates_and_rebuild_linked_chunk() {
2308                let event_cache_store =
2309                    get_event_cache_store().await.unwrap().into_event_cache_store();
2310                event_cache_store.test_handle_updates_and_rebuild_linked_chunk().await;
2311            }
2312
2313            #[async_test]
2314            async fn test_linked_chunk_exists_before_referenced() {
2315                let event_cache_store =
2316                    get_event_cache_store().await.unwrap().into_event_cache_store();
2317                event_cache_store.test_linked_chunk_exists_before_referenced().await;
2318            }
2319
2320            #[async_test]
2321            async fn test_linked_chunk_allow_same_event_in_room_and_thread() {
2322                let event_cache_store =
2323                    get_event_cache_store().await.unwrap().into_event_cache_store();
2324                event_cache_store.test_linked_chunk_allows_same_event_in_room_and_thread().await;
2325            }
2326
2327            #[async_test]
2328            async fn test_load_last_chunk() {
2329                let event_cache_store =
2330                    get_event_cache_store().await.unwrap().into_event_cache_store();
2331                event_cache_store.test_load_last_chunk().await;
2332            }
2333
2334            #[async_test]
2335            async fn test_load_last_chunk_with_a_cycle() {
2336                let event_cache_store =
2337                    get_event_cache_store().await.unwrap().into_event_cache_store();
2338                event_cache_store.test_load_last_chunk_with_a_cycle().await;
2339            }
2340
2341            #[async_test]
2342            async fn test_load_previous_chunk() {
2343                let event_cache_store =
2344                    get_event_cache_store().await.unwrap().into_event_cache_store();
2345                event_cache_store.test_load_previous_chunk().await;
2346            }
2347
2348            #[async_test]
2349            async fn test_linked_chunk_incremental_loading() {
2350                let event_cache_store =
2351                    get_event_cache_store().await.unwrap().into_event_cache_store();
2352                event_cache_store.test_linked_chunk_incremental_loading().await;
2353            }
2354
2355            #[async_test]
2356            async fn test_linked_chunk_remove_chunk() {
2357                let event_cache_store =
2358                    get_event_cache_store().await.unwrap().into_event_cache_store();
2359                event_cache_store.test_linked_chunk_remove_chunk().await;
2360            }
2361
2362            #[async_test]
2363            async fn test_linked_chunk_replace_item() {
2364                let event_cache_store =
2365                    get_event_cache_store().await.unwrap().into_event_cache_store();
2366                event_cache_store.test_linked_chunk_replace_item().await;
2367            }
2368
2369            #[async_test]
2370            async fn test_linked_chunk_remove_item() {
2371                let event_cache_store =
2372                    get_event_cache_store().await.unwrap().into_event_cache_store();
2373                event_cache_store.test_linked_chunk_remove_item().await;
2374            }
2375
2376            #[async_test]
2377            async fn test_linked_chunk_detach_last_items() {
2378                let event_cache_store =
2379                    get_event_cache_store().await.unwrap().into_event_cache_store();
2380                event_cache_store.test_linked_chunk_detach_last_items().await;
2381            }
2382
2383            #[async_test]
2384            async fn test_linked_chunk_start_end_reattach_items() {
2385                let event_cache_store =
2386                    get_event_cache_store().await.unwrap().into_event_cache_store();
2387                event_cache_store.test_linked_chunk_start_end_reattach_items().await;
2388            }
2389
2390            #[async_test]
2391            async fn test_linked_chunk_clear() {
2392                let event_cache_store =
2393                    get_event_cache_store().await.unwrap().into_event_cache_store();
2394                event_cache_store.test_linked_chunk_clear().await;
2395            }
2396
2397            #[async_test]
2398            async fn test_linked_chunk_clear_and_reinsert() {
2399                let event_cache_store =
2400                    get_event_cache_store().await.unwrap().into_event_cache_store();
2401                event_cache_store.test_linked_chunk_clear_and_reinsert().await;
2402            }
2403
2404            #[async_test]
2405            async fn test_rebuild_empty_linked_chunk() {
2406                let event_cache_store =
2407                    get_event_cache_store().await.unwrap().into_event_cache_store();
2408                event_cache_store.test_rebuild_empty_linked_chunk().await;
2409            }
2410
2411            #[async_test]
2412            async fn test_linked_chunk_multiple_rooms() {
2413                let event_cache_store =
2414                    get_event_cache_store().await.unwrap().into_event_cache_store();
2415                event_cache_store.test_linked_chunk_multiple_rooms().await;
2416            }
2417
2418            #[async_test]
2419            async fn test_load_all_chunks_metadata() {
2420                let event_cache_store =
2421                    get_event_cache_store().await.unwrap().into_event_cache_store();
2422                event_cache_store.test_load_all_chunks_metadata().await;
2423            }
2424
2425            #[async_test]
2426            async fn test_clear_all_linked_chunks() {
2427                let event_cache_store =
2428                    get_event_cache_store().await.unwrap().into_event_cache_store();
2429                event_cache_store.test_clear_all_linked_chunks().await;
2430            }
2431
2432            #[async_test]
2433            async fn test_remove_room() {
2434                let event_cache_store =
2435                    get_event_cache_store().await.unwrap().into_event_cache_store();
2436                event_cache_store.test_remove_room().await;
2437            }
2438
2439            #[async_test]
2440            async fn test_filter_duplicated_events() {
2441                let event_cache_store =
2442                    get_event_cache_store().await.unwrap().into_event_cache_store();
2443                event_cache_store.test_filter_duplicated_events().await;
2444            }
2445
2446            #[async_test]
2447            async fn test_filter_duplicate_events_no_events() {
2448                let event_cache_store =
2449                    get_event_cache_store().await.unwrap().into_event_cache_store();
2450                event_cache_store.test_filter_duplicate_events_no_events().await;
2451            }
2452
2453            #[async_test]
2454            async fn test_find_event() {
2455                let event_cache_store =
2456                    get_event_cache_store().await.unwrap().into_event_cache_store();
2457                event_cache_store.test_find_event().await;
2458            }
2459
2460            #[async_test]
2461            async fn test_find_event_when_event_in_room_and_thread() {
2462                let event_cache_store =
2463                    get_event_cache_store().await.unwrap().into_event_cache_store();
2464                event_cache_store.test_find_event_when_event_in_room_and_thread().await;
2465            }
2466
2467            #[async_test]
2468            async fn test_find_event_relations() {
2469                let event_cache_store =
2470                    get_event_cache_store().await.unwrap().into_event_cache_store();
2471                event_cache_store.test_find_event_relations().await;
2472            }
2473
2474            #[async_test]
2475            async fn test_find_event_relations_when_event_in_room_and_thread() {
2476                let event_cache_store =
2477                    get_event_cache_store().await.unwrap().into_event_cache_store();
2478                event_cache_store.test_find_event_relations_when_event_in_room_and_thread().await;
2479            }
2480
2481            #[async_test]
2482            async fn test_get_room_events() {
2483                let event_cache_store =
2484                    get_event_cache_store().await.unwrap().into_event_cache_store();
2485                event_cache_store.test_get_room_events().await;
2486            }
2487
2488            #[async_test]
2489            async fn test_get_room_events_filtered() {
2490                let event_cache_store =
2491                    get_event_cache_store().await.unwrap().into_event_cache_store();
2492                event_cache_store.test_get_room_events_filtered().await;
2493            }
2494
2495            #[async_test]
2496            async fn test_get_room_events_with_event_in_room_and_thread() {
2497                let event_cache_store =
2498                    get_event_cache_store().await.unwrap().into_event_cache_store();
2499                event_cache_store.test_get_room_events_with_event_in_room_and_thread().await;
2500            }
2501
2502            #[async_test]
2503            async fn test_save_event() {
2504                let event_cache_store =
2505                    get_event_cache_store().await.unwrap().into_event_cache_store();
2506                event_cache_store.test_save_event().await;
2507            }
2508
2509            #[async_test]
2510            async fn test_save_event_updates_event_in_room_and_thread() {
2511                let event_cache_store =
2512                    get_event_cache_store().await.unwrap().into_event_cache_store();
2513                event_cache_store.test_save_event_updates_event_in_room_and_thread().await;
2514            }
2515
2516            #[async_test]
2517            async fn test_thread_vs_room_linked_chunk() {
2518                let event_cache_store =
2519                    get_event_cache_store().await.unwrap().into_event_cache_store();
2520                event_cache_store.test_thread_vs_room_linked_chunk().await;
2521            }
2522        }
2523    };
2524}
2525
2526/// Macro generating tests for the event cache store, related to time (mostly
2527/// for the cross-process lock).
2528#[allow(unused_macros)]
2529#[macro_export]
2530macro_rules! event_cache_store_integration_tests_time {
2531    () => {
2532        mod event_cache_store_integration_tests_time {
2533            use std::time::Duration;
2534
2535            #[cfg(all(target_family = "wasm", target_os = "unknown"))]
2536            use gloo_timers::future::sleep;
2537            use matrix_sdk_test::async_test;
2538            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2539            use tokio::time::sleep;
2540            use $crate::event_cache::store::IntoEventCacheStore;
2541
2542            use super::get_event_cache_store;
2543
2544            #[async_test]
2545            async fn test_lease_locks() {
2546                let store = get_event_cache_store().await.unwrap().into_event_cache_store();
2547
2548                let acquired0 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
2549                assert_eq!(acquired0, Some(1)); // first lock generation
2550
2551                // Should extend the lease automatically (same holder).
2552                let acquired2 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
2553                assert_eq!(acquired2, Some(1)); // same lock generation
2554
2555                // Should extend the lease automatically (same holder + time is ok).
2556                let acquired3 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
2557                assert_eq!(acquired3, Some(1)); // same lock generation
2558
2559                // Another attempt at taking the lock should fail, because it's taken.
2560                let acquired4 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
2561                assert!(acquired4.is_none()); // not acquired
2562
2563                // Even if we insist.
2564                let acquired5 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
2565                assert!(acquired5.is_none()); // not acquired
2566
2567                // That's a nice test we got here, go take a little nap.
2568                sleep(Duration::from_millis(50)).await;
2569
2570                // Still too early.
2571                let acquired55 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
2572                assert!(acquired55.is_none()); // not acquired
2573
2574                // Ok you can take another nap then.
2575                sleep(Duration::from_millis(250)).await;
2576
2577                // At some point, we do get the lock.
2578                let acquired6 = store.try_take_leased_lock(0, "key", "bob").await.unwrap();
2579                assert_eq!(acquired6, Some(2)); // new lock generation!
2580
2581                sleep(Duration::from_millis(1)).await;
2582
2583                // The other gets it almost immediately too.
2584                let acquired7 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
2585                assert_eq!(acquired7, Some(3)); // new lock generation!
2586
2587                sleep(Duration::from_millis(1)).await;
2588
2589                // But when we take a longer lease…
2590                let acquired8 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
2591                assert_eq!(acquired8, Some(4)); // new lock generation!
2592
2593                // It blocks the other user.
2594                let acquired9 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
2595                assert!(acquired9.is_none()); // not acquired
2596
2597                // We can hold onto our lease.
2598                let acquired10 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
2599                assert_eq!(acquired10, Some(4)); // same lock generation
2600            }
2601        }
2602    };
2603}