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