Skip to main content

matrix_sdk_base/event_cache/store/
integration_tests.rs

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