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 `None` because we don't want to create it if missing.
1649        assert!(self.load_thread_info(room_id, thread_id, false).await.unwrap().is_none());
1650
1651        // Load for the second time.
1652        //
1653        // We must get an empty `ThreadInfo` because we want to create if if
1654        // missing.
1655        let ThreadInfo { number_of_replies: _, latest_event: _, read_receipts } =
1656            self.load_thread_info(room_id, thread_id, true).await.unwrap().unwrap();
1657        let ReadReceipts { num_unread, num_notifications, num_mentions, latest_active, pending } =
1658            read_receipts;
1659        assert_eq!(num_unread, 0);
1660        assert_eq!(num_notifications, 0);
1661        assert_eq!(num_mentions, 0);
1662        assert!(latest_active.is_none());
1663        assert!(pending.is_empty());
1664
1665        // Load for the third time.
1666        //
1667        // We must get the same empty `ThreadInfo`, even if we don't want to
1668        // create it if missing.
1669        let mut thread_info =
1670            self.load_thread_info(room_id, thread_id, false).await.unwrap().unwrap();
1671        let ThreadInfo { number_of_replies: _, latest_event: _, read_receipts } = &thread_info;
1672        let ReadReceipts { num_unread, num_notifications, num_mentions, latest_active, pending } =
1673            read_receipts;
1674        assert_eq!(*num_unread, 0);
1675        assert_eq!(*num_notifications, 0);
1676        assert_eq!(*num_mentions, 0);
1677        assert!(latest_active.is_none());
1678        assert!(pending.is_empty());
1679
1680        // Update the `ThreadInfo`.
1681        thread_info.read_receipts.num_unread = 1;
1682        thread_info.read_receipts.num_notifications = 2;
1683        self.update_thread_info(room_id, thread_id, &thread_info).await.unwrap();
1684
1685        // Load for the fourth time.
1686        //
1687        // We must get the updated `ThreadInfo`.
1688        let ThreadInfo { number_of_replies: _, latest_event: _, read_receipts } =
1689            self.load_thread_info(room_id, thread_id, true).await.unwrap().unwrap();
1690        let ReadReceipts { num_unread, num_notifications, num_mentions, latest_active, pending } =
1691            read_receipts;
1692        assert_eq!(num_unread, 1);
1693        assert_eq!(num_notifications, 2);
1694        assert_eq!(num_mentions, 0);
1695        assert!(latest_active.is_none());
1696        assert!(pending.is_empty());
1697    }
1698
1699    async fn test_clear_all_events(&self) {
1700        let linked_chunk_ids = [
1701            LinkedChunkId::Room(room_id!("!r0")),
1702            LinkedChunkId::Thread(room_id!("!r1"), event_id!("$r1_thread_root0")),
1703            LinkedChunkId::PinnedEvents(room_id!("!r2")),
1704            // `LinkedChunkId::EventFocused` are not persisted in the database, no need to test it.
1705        ];
1706
1707        // Create data for each `LinkedChunkId`.
1708        for linked_chunk_id in linked_chunk_ids {
1709            let room_id = linked_chunk_id.room_id();
1710
1711            // Assume the thread has been “remembered” correctly (this is done
1712            // in `ThreadEventCacheState::new`).
1713            if let LinkedChunkId::Thread(_, thread_id) = &linked_chunk_id {
1714                self.load_thread_info(room_id, thread_id, true).await.unwrap();
1715            }
1716
1717            self.handle_linked_chunk_updates(
1718                linked_chunk_id,
1719                vec![
1720                    // New chunk
1721                    Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1722                    // New items on 0.
1723                    Update::PushItems {
1724                        at: Position::new(CId::new(0), 0),
1725                        items: vec![
1726                            make_test_event(room_id, "foo"),
1727                            make_test_event(room_id, "bar"),
1728                            make_test_event(room_id, "baz"),
1729                        ],
1730                    },
1731                ],
1732            )
1733            .await
1734            .unwrap();
1735
1736            // Linked chunks all exist!
1737            assert!(
1738                lazy_loader::from_all_chunks::<3, _, _>(
1739                    self.load_all_chunks(linked_chunk_id).await.unwrap()
1740                )
1741                .unwrap()
1742                .is_some()
1743            );
1744
1745            // Events exist!
1746            assert_eq!(self.get_room_events(room_id, None, None).await.unwrap().len(), 3);
1747        }
1748
1749        // Clear all events!
1750        self.clear_all_events(None).await.unwrap();
1751
1752        // Check all data have been removed, forever.
1753        for linked_chunk_id in linked_chunk_ids {
1754            let room_id = linked_chunk_id.room_id();
1755
1756            // No more linked chunks!
1757            assert!(
1758                lazy_loader::from_all_chunks::<3, _, _>(
1759                    self.load_all_chunks(linked_chunk_id).await.unwrap()
1760                )
1761                .unwrap()
1762                .is_none()
1763            );
1764
1765            // No more events!
1766            assert!(self.get_room_events(room_id, None, None).await.unwrap().is_empty());
1767        }
1768    }
1769
1770    async fn test_clear_all_events_for_specific_room(&self) {
1771        let linked_chunk_ids_for_room_0 = [
1772            LinkedChunkId::Room(room_id!("!r0")),
1773            LinkedChunkId::Thread(room_id!("!r0"), event_id!("$r0_thread_root")),
1774            LinkedChunkId::PinnedEvents(room_id!("!r0")),
1775        ];
1776        let linked_chunk_ids_for_room_1 = [
1777            LinkedChunkId::Room(room_id!("!r1")),
1778            LinkedChunkId::Thread(room_id!("!r1"), event_id!("$r1_thread_root")),
1779            LinkedChunkId::PinnedEvents(room_id!("!r1")),
1780        ];
1781        let linked_chunk_ids_for_room_2 = [
1782            LinkedChunkId::Room(room_id!("!r2")),
1783            LinkedChunkId::Thread(room_id!("!r2"), event_id!("$r2_thread_root")),
1784            LinkedChunkId::PinnedEvents(room_id!("!r2")),
1785        ];
1786
1787        // Create data for each `LinkedChunkId`.
1788        for linked_chunk_id in linked_chunk_ids_for_room_0
1789            .iter()
1790            .chain(&linked_chunk_ids_for_room_1)
1791            .chain(&linked_chunk_ids_for_room_2)
1792        {
1793            let room_id = linked_chunk_id.room_id();
1794
1795            // Assume the thread has been “remembered” correctly (this is done
1796            // in `ThreadEventCacheState::new`).
1797            if let LinkedChunkId::Thread(_, thread_id) = &linked_chunk_id {
1798                self.load_thread_info(room_id, thread_id, true).await.unwrap();
1799            }
1800
1801            self.handle_linked_chunk_updates(
1802                *linked_chunk_id,
1803                vec![
1804                    // New chunk
1805                    Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1806                    // New items on 0.
1807                    Update::PushItems {
1808                        at: Position::new(CId::new(0), 0),
1809                        items: vec![
1810                            make_test_event(room_id, "foo"),
1811                            make_test_event(room_id, "bar"),
1812                            make_test_event(room_id, "baz"),
1813                        ],
1814                    },
1815                ],
1816            )
1817            .await
1818            .unwrap();
1819
1820            // Linked chunks all exist!
1821            assert!(
1822                lazy_loader::from_all_chunks::<3, _, _>(
1823                    self.load_all_chunks(*linked_chunk_id).await.unwrap()
1824                )
1825                .unwrap()
1826                .is_some()
1827            );
1828
1829            // Events exist!
1830            assert_eq!(self.get_room_events(room_id, None, None).await.unwrap().len(), 3);
1831        }
1832
1833        // Clear all events for room 1 **ONLY**!
1834        self.clear_all_events(Some(linked_chunk_ids_for_room_1[0].room_id())).await.unwrap();
1835
1836        // Check all data have been removed for room 1 **ONLY**, forever.
1837        for linked_chunk_id in linked_chunk_ids_for_room_1 {
1838            let room_id = linked_chunk_id.room_id();
1839
1840            // No more linked chunks!
1841            assert!(
1842                lazy_loader::from_all_chunks::<3, _, _>(
1843                    self.load_all_chunks(linked_chunk_id).await.unwrap()
1844                )
1845                .unwrap()
1846                .is_none()
1847            );
1848
1849            // No more events!
1850            assert!(self.get_room_events(room_id, None, None).await.unwrap().is_empty());
1851        }
1852
1853        // Check all the other data are untouched.
1854        for linked_chunk_id in
1855            linked_chunk_ids_for_room_0.iter().chain(&linked_chunk_ids_for_room_2)
1856        {
1857            let room_id = linked_chunk_id.room_id();
1858
1859            // Linked chunks all exist!
1860            assert!(
1861                lazy_loader::from_all_chunks::<3, _, _>(
1862                    self.load_all_chunks(*linked_chunk_id).await.unwrap()
1863                )
1864                .unwrap()
1865                .is_some()
1866            );
1867
1868            // Events exist!
1869            assert_eq!(self.get_room_events(room_id, None, None).await.unwrap().len(), 3);
1870        }
1871    }
1872
1873    async fn test_filter_duplicated_events(&self) {
1874        let room_id = room_id!("!r0:matrix.org");
1875        let linked_chunk_id = LinkedChunkId::Room(room_id);
1876        let another_room_id = room_id!("!r1:matrix.org");
1877        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
1878        let event = |msg: &str| make_test_event(room_id, msg);
1879
1880        let event_comte = event("comté");
1881        let event_brigand = event("brigand du jorat");
1882        let event_raclette = event("raclette");
1883        let event_morbier = event("morbier");
1884        let event_gruyere = event("gruyère");
1885        let event_tome = event("tome");
1886        let event_mont_dor = event("mont d'or");
1887
1888        self.handle_linked_chunk_updates(
1889            linked_chunk_id,
1890            vec![
1891                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1892                Update::PushItems {
1893                    at: Position::new(CId::new(0), 0),
1894                    items: vec![event_comte.clone(), event_brigand.clone()],
1895                },
1896                Update::NewGapChunk {
1897                    previous: Some(CId::new(0)),
1898                    new: CId::new(1),
1899                    next: None,
1900                    gap: Gap { token: "brillat-savarin".to_owned() },
1901                },
1902                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
1903                Update::PushItems {
1904                    at: Position::new(CId::new(2), 0),
1905                    items: vec![event_morbier.clone(), event_mont_dor.clone()],
1906                },
1907            ],
1908        )
1909        .await
1910        .unwrap();
1911
1912        // Add other events in another room, to ensure filtering take the
1913        // `room_id` into account.
1914        self.handle_linked_chunk_updates(
1915            another_linked_chunk_id,
1916            vec![
1917                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1918                Update::PushItems {
1919                    at: Position::new(CId::new(0), 0),
1920                    items: vec![event_tome.clone()],
1921                },
1922            ],
1923        )
1924        .await
1925        .unwrap();
1926
1927        let duplicated_events = BTreeMap::from_iter(
1928            self.filter_duplicated_events(
1929                linked_chunk_id,
1930                vec![
1931                    event_comte.event_id().unwrap().to_owned(),
1932                    event_raclette.event_id().unwrap().to_owned(),
1933                    event_morbier.event_id().unwrap().to_owned(),
1934                    event_gruyere.event_id().unwrap().to_owned(),
1935                    event_tome.event_id().unwrap().to_owned(),
1936                    event_mont_dor.event_id().unwrap().to_owned(),
1937                ],
1938            )
1939            .await
1940            .unwrap(),
1941        );
1942
1943        assert_eq!(duplicated_events.len(), 3);
1944
1945        assert_eq!(
1946            *duplicated_events.get(event_comte.event_id().unwrap()).unwrap(),
1947            Position::new(CId::new(0), 0)
1948        );
1949        assert_eq!(
1950            *duplicated_events.get(event_morbier.event_id().unwrap()).unwrap(),
1951            Position::new(CId::new(2), 0)
1952        );
1953        assert_eq!(
1954            *duplicated_events.get(event_mont_dor.event_id().unwrap()).unwrap(),
1955            Position::new(CId::new(2), 1)
1956        );
1957    }
1958
1959    async fn test_filter_duplicate_events_no_events(&self) {
1960        let room_id = *DEFAULT_TEST_ROOM_ID;
1961        let linked_chunk_id = LinkedChunkId::Room(room_id);
1962        let duplicates = self.filter_duplicated_events(linked_chunk_id, Vec::new()).await.unwrap();
1963        assert!(duplicates.is_empty());
1964    }
1965
1966    async fn test_find_event(&self) {
1967        let room_id = room_id!("!r0:matrix.org");
1968        let another_room_id = room_id!("!r1:matrix.org");
1969        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
1970        let event = |msg: &str| make_test_event(room_id, msg);
1971
1972        let event_comte = event("comté");
1973        let event_gruyere = event("gruyère");
1974
1975        // Add one event in one room.
1976        self.handle_linked_chunk_updates(
1977            LinkedChunkId::Room(room_id),
1978            vec![
1979                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1980                Update::PushItems {
1981                    at: Position::new(CId::new(0), 0),
1982                    items: vec![event_comte.clone()],
1983                },
1984            ],
1985        )
1986        .await
1987        .unwrap();
1988
1989        // Add another event in another room.
1990        self.handle_linked_chunk_updates(
1991            another_linked_chunk_id,
1992            vec![
1993                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1994                Update::PushItems {
1995                    at: Position::new(CId::new(0), 0),
1996                    items: vec![event_gruyere.clone()],
1997                },
1998            ],
1999        )
2000        .await
2001        .unwrap();
2002
2003        // Now let's find the event.
2004        let event = self
2005            .find_event(room_id, event_comte.event_id().unwrap())
2006            .await
2007            .expect("failed to query for finding an event")
2008            .expect("failed to find an event");
2009
2010        assert_eq!(event.event_id(), event_comte.event_id());
2011
2012        // Now let's try to find an event that exists, but not in the expected
2013        // room.
2014        assert!(
2015            self.find_event(room_id, event_gruyere.event_id().unwrap())
2016                .await
2017                .expect("failed to query for finding an event")
2018                .is_none()
2019        );
2020
2021        // Clearing the rooms also clears the event's storage.
2022        self.clear_all_events(None).await.expect("failed to clear all rooms chunks");
2023        assert!(
2024            self.find_event(room_id, event_comte.event_id().unwrap())
2025                .await
2026                .expect("failed to query for finding an event")
2027                .is_none()
2028        );
2029    }
2030
2031    async fn test_find_event_when_event_in_room_and_thread(&self) {
2032        let room_id = *DEFAULT_TEST_ROOM_ID;
2033        let thread_root = event_id!("$thread_root");
2034
2035        // Create an event that will be only be inserted into the room
2036        let room_event_id = event_id!("$room_event");
2037        let room_event = make_test_event_with_event_id(room_id, "room event", Some(room_event_id));
2038
2039        // Create an event that will only be inserted into the thread
2040        let thread_event_id = event_id!("$thread_event");
2041        let thread_event =
2042            make_test_event_with_event_id(room_id, "thread event", Some(thread_event_id));
2043
2044        // Create an event that will be inserted into both the room and thread
2045        // linked chunks.
2046        let room_and_thread_event_id = event_id!("$room_and_thread");
2047        let room_and_thread_event = make_test_event_with_event_id(
2048            room_id,
2049            "room and thread",
2050            Some(room_and_thread_event_id),
2051        );
2052
2053        let room_linked_chunk_id = LinkedChunkId::Room(room_id);
2054        let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
2055
2056        // Insert the relevant events into the room's linked chunk.
2057        self.handle_linked_chunk_updates(
2058            room_linked_chunk_id,
2059            vec![
2060                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2061                Update::PushItems {
2062                    at: Position::new(CId::new(1), 0),
2063                    items: vec![room_event, room_and_thread_event.clone()],
2064                },
2065            ],
2066        )
2067        .await
2068        .unwrap();
2069
2070        // Insert the relevant events into the thread's linked chunk.
2071        self.handle_linked_chunk_updates(
2072            thread_linked_chunk_id,
2073            vec![
2074                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2075                Update::PushItems {
2076                    at: Position::new(CId::new(1), 0),
2077                    items: vec![thread_event, room_and_thread_event],
2078                },
2079            ],
2080        )
2081        .await
2082        .unwrap();
2083
2084        // Verify that event that is only in the room can be retrieved
2085        assert_matches!(self.find_event(room_id, room_event_id).await, Ok(Some(event)) => {
2086            assert_eq!(event.event_id().unwrap(), room_event_id)
2087        });
2088
2089        // Verify that the event that is only in the thread can be retrieved
2090        assert_matches!(self.find_event(room_id, thread_event_id).await, Ok(Some(event)) => {
2091            assert_eq!(event.event_id().unwrap(), thread_event_id)
2092        });
2093
2094        // Verify that event that is in both room and thread can be retrieved
2095        assert_matches!(self.find_event(room_id, room_and_thread_event_id).await, Ok(Some(event)) => {
2096            assert_eq!(event.event_id().unwrap(), room_and_thread_event_id);
2097        });
2098    }
2099
2100    async fn test_find_event_relations(&self) {
2101        let room_id = room_id!("!r0:matrix.org");
2102        let another_room_id = room_id!("!r1:matrix.org");
2103
2104        let f = EventFactory::new().room(room_id).sender(*ALICE);
2105
2106        // Create event and related events for the first room.
2107        let eid1 = event_id!("$event1:matrix.org");
2108        let e1 = f.text_msg("comter").event_id(eid1).into_event();
2109
2110        let edit_eid1 = event_id!("$edit_event1:matrix.org");
2111        let edit_e1 = f
2112            .text_msg("* comté")
2113            .event_id(edit_eid1)
2114            .edit(eid1, RoomMessageEventContentWithoutRelation::text_plain("comté"))
2115            .into_event();
2116
2117        let reaction_eid1 = event_id!("$reaction_event1:matrix.org");
2118        let reaction_e1 = f.reaction(eid1, "👍").event_id(reaction_eid1).into_event();
2119
2120        let eid2 = event_id!("$event2:matrix.org");
2121        let e2 = f.text_msg("galette saucisse").event_id(eid2).into_event();
2122
2123        // Create events for the second room.
2124        let f = f.room(another_room_id);
2125
2126        let eid3 = event_id!("$event3:matrix.org");
2127        let e3 = f.text_msg("gruyère").event_id(eid3).into_event();
2128
2129        let reaction_eid3 = event_id!("$reaction_event3:matrix.org");
2130        let reaction_e3 = f.reaction(eid3, "👍").event_id(reaction_eid3).into_event();
2131
2132        // Save All The Things!
2133        self.save_event(room_id, e1).await.unwrap();
2134        self.save_event(room_id, edit_e1).await.unwrap();
2135        self.save_event(room_id, reaction_e1.clone()).await.unwrap();
2136        self.save_event(room_id, e2).await.unwrap();
2137        self.save_event(another_room_id, e3).await.unwrap();
2138        self.save_event(another_room_id, reaction_e3).await.unwrap();
2139
2140        // Finding relations without a filter returns all of them.
2141        let relations = self.find_event_relations(room_id, eid1, None).await.unwrap();
2142        assert_eq!(relations.len(), 2);
2143        // The position is `None` for items outside the linked chunk.
2144        assert!(
2145            relations.iter().any(|(ev, pos)| ev.event_id() == Some(edit_eid1) && pos.is_none())
2146        );
2147        assert!(
2148            relations.iter().any(|(ev, pos)| ev.event_id() == Some(reaction_eid1) && pos.is_none())
2149        );
2150
2151        // Finding relations with a filter only returns a subset.
2152        let relations = self
2153            .find_event_relations(room_id, eid1, Some(&[RelationType::Replacement]))
2154            .await
2155            .unwrap();
2156        assert_eq!(relations.len(), 1);
2157        assert_eq!(relations[0].0.event_id(), Some(edit_eid1));
2158
2159        let relations = self
2160            .find_event_relations(
2161                room_id,
2162                eid1,
2163                Some(&[RelationType::Replacement, RelationType::Annotation]),
2164            )
2165            .await
2166            .unwrap();
2167        assert_eq!(relations.len(), 2);
2168        assert!(relations.iter().any(|r| r.0.event_id() == Some(edit_eid1)));
2169        assert!(relations.iter().any(|r| r.0.event_id() == Some(reaction_eid1)));
2170
2171        // We can't find relations using the wrong room.
2172        let relations = self
2173            .find_event_relations(another_room_id, eid1, Some(&[RelationType::Replacement]))
2174            .await
2175            .unwrap();
2176        assert!(relations.is_empty());
2177
2178        // But if an event exists in the linked chunk, we may have its position
2179        // when it's found as a relationship.
2180
2181        // Add reaction_e1 to the room's linked chunk.
2182        self.handle_linked_chunk_updates(
2183            LinkedChunkId::Room(room_id),
2184            vec![
2185                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2186                Update::PushItems { at: Position::new(CId::new(0), 0), items: vec![reaction_e1] },
2187            ],
2188        )
2189        .await
2190        .unwrap();
2191
2192        // When looking for aggregations to e1, we should have the position for
2193        // reaction_e1.
2194        let relations = self.find_event_relations(room_id, eid1, None).await.unwrap();
2195
2196        // The position is set for `reaction_eid1` now.
2197        assert!(relations.iter().any(|(ev, pos)| {
2198            ev.event_id() == Some(reaction_eid1) && *pos == Some(Position::new(CId::new(0), 0))
2199        }));
2200
2201        // But it's still not set for the other related events.
2202        assert!(
2203            relations.iter().any(|(ev, pos)| ev.event_id() == Some(edit_eid1) && pos.is_none())
2204        );
2205    }
2206
2207    async fn test_find_event_relations_when_event_in_room_and_thread(&self) {
2208        let room_id = *DEFAULT_TEST_ROOM_ID;
2209        let thread_root = event_id!("$thread_root");
2210
2211        // Create an event that will inserted into both the room and thread
2212        // linked chunks.
2213        let event_id = event_id!("$event");
2214        let event = make_test_event_with_event_id(room_id, "event", Some(event_id));
2215
2216        // Create an event that will only be inserted into the thread in order
2217        // to help distinguish between the room and thread linked chunks.
2218        let extra_thread_event_id = event_id!("$extra_thread_event");
2219        let extra_thread_event = make_test_event_with_event_id(
2220            room_id,
2221            "extra thread event",
2222            Some(extra_thread_event_id),
2223        );
2224
2225        // Create a reaction that will only be inserted into the room
2226        let room_reaction_id = event_id!("$room_reaction");
2227        let room_reaction = EventFactory::new()
2228            .room(room_id)
2229            .sender(*ALICE)
2230            .reaction(event_id, "room")
2231            .event_id(room_reaction_id)
2232            .into_event();
2233
2234        // Create a reaction that will only be inserted into the thread
2235        let thread_reaction_id = event_id!("$thread_reaction");
2236        let thread_reaction = EventFactory::new()
2237            .room(room_id)
2238            .sender(*ALICE)
2239            .reaction(event_id, "thread")
2240            .event_id(thread_reaction_id)
2241            .into_event();
2242
2243        // Create a reaction that will be inserted into both the room and thread
2244        // linked chunks.
2245        let room_and_thread_reaction_id = event_id!("$room_and_thread_reaction");
2246        let room_and_thread_reaction = EventFactory::new()
2247            .room(room_id)
2248            .sender(*ALICE)
2249            .reaction(event_id, "room and thread")
2250            .event_id(room_and_thread_reaction_id)
2251            .into_event();
2252
2253        let room_linked_chunk_id = LinkedChunkId::Room(room_id);
2254        let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
2255
2256        // Insert the relevant events into the room's linked chunk.
2257        self.handle_linked_chunk_updates(
2258            room_linked_chunk_id,
2259            vec![
2260                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2261                Update::PushItems {
2262                    at: Position::new(CId::new(1), 0),
2263                    items: vec![event.clone(), room_reaction, room_and_thread_reaction.clone()],
2264                },
2265            ],
2266        )
2267        .await
2268        .unwrap();
2269
2270        // Insert the relevant events into the thread's linked chunk.
2271        self.handle_linked_chunk_updates(
2272            thread_linked_chunk_id,
2273            vec![
2274                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2275                Update::PushItems {
2276                    at: Position::new(CId::new(1), 0),
2277                    items: vec![
2278                        event.clone(),
2279                        extra_thread_event,
2280                        thread_reaction,
2281                        room_and_thread_reaction,
2282                    ],
2283                },
2284            ],
2285        )
2286        .await
2287        .unwrap();
2288
2289        // Verify that only related events from the room are returned
2290        assert_matches!(self.find_event_relations(room_id, event_id, None).await, Ok(relations) => {
2291            assert_eq!(relations.len(), 3);
2292            // Verify that room reaction is in the list and associated with its
2293            // position in the room linked chunk.
2294            let room_relation = relations
2295                .iter()
2296                .find(|relation| relation.0.event_id().unwrap() == room_reaction_id)
2297                .unwrap();
2298            assert_matches!(room_relation, (_, Some(position)) => {
2299                assert_eq!(*position, Position::new(CId::new(1), 1));
2300            });
2301
2302            // Verify that thread reaction is in the list and not associated
2303            // with a position, as all positions are provided for the room
2304            // linked chunk.
2305            let thread_relation = relations
2306                .iter()
2307                .find(|relation| relation.0.event_id().unwrap() == thread_reaction_id)
2308                .unwrap();
2309            assert_matches!(thread_relation, (_, None));
2310
2311            // Verify that room and thread reaction is in the list and
2312            // associated with its position in the room linked chunk, not the
2313            // thread linked chunk.
2314            let room_and_thread_relation = relations
2315                .iter()
2316                .find(|relation| relation.0.event_id().unwrap() == room_and_thread_reaction_id)
2317                .unwrap();
2318            assert_matches!(room_and_thread_relation, (_, Some(position)) => {
2319                assert_eq!(*position, Position::new(CId::new(1), 2));
2320            });
2321        });
2322    }
2323
2324    async fn test_get_room_events(&self) {
2325        let room_id = room_id!("!r0:matrix.org");
2326        let another_room_id = room_id!("!r1:matrix.org");
2327        let linked_chunk_id = LinkedChunkId::Room(room_id);
2328        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
2329        let event = |msg: &str| make_test_event(room_id, msg);
2330
2331        let event_comte = event("comté");
2332        let event_gruyere = event("gruyère");
2333        let event_stilton = event("stilton");
2334
2335        // Add one event in one room.
2336        self.handle_linked_chunk_updates(
2337            linked_chunk_id,
2338            vec![
2339                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2340                Update::PushItems {
2341                    at: Position::new(CId::new(0), 0),
2342                    items: vec![event_comte.clone(), event_gruyere.clone()],
2343                },
2344            ],
2345        )
2346        .await
2347        .unwrap();
2348
2349        // Add an event in a different room.
2350        self.handle_linked_chunk_updates(
2351            another_linked_chunk_id,
2352            vec![
2353                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2354                Update::PushItems {
2355                    at: Position::new(CId::new(0), 0),
2356                    items: vec![event_stilton.clone()],
2357                },
2358            ],
2359        )
2360        .await
2361        .unwrap();
2362
2363        // Now let's find the events.
2364        let events = self
2365            .get_room_events(room_id, None, None)
2366            .await
2367            .expect("failed to query for room events");
2368
2369        assert_eq!(events.len(), 2);
2370
2371        let got_ids: Vec<_> =
2372            events.into_iter().map(|ev| ev.event_id().map(ToOwned::to_owned)).collect();
2373        let expected_ids = vec![
2374            event_comte.event_id().map(ToOwned::to_owned),
2375            event_gruyere.event_id().map(ToOwned::to_owned),
2376        ];
2377
2378        for expected in expected_ids {
2379            assert!(
2380                got_ids.contains(&expected),
2381                "Expected event {expected:?} not in got events: {got_ids:?}."
2382            );
2383        }
2384    }
2385
2386    async fn test_get_room_events_filtered(&self) {
2387        macro_rules! assert_expected_events {
2388            ($events:expr, [$($item:expr),* $(,)?]) => {{
2389                let got_ids: BTreeSet<_> = $events.into_iter().map(|ev| ev.event_id().map(ToOwned::to_owned)).flatten().collect();
2390                let expected_ids = BTreeSet::from([$($item.event_id().unwrap().to_owned()),*]);
2391
2392                assert_eq!(got_ids, expected_ids);
2393            }};
2394        }
2395
2396        let room_id = room_id!("!r0:matrix.org");
2397        let linked_chunk_id = LinkedChunkId::Room(room_id);
2398        let another_room_id = room_id!("!r1:matrix.org");
2399        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
2400
2401        let event = |session_id: &str| make_encrypted_test_event(room_id, session_id);
2402
2403        let first_event = event("session_1");
2404        let second_event = event("session_2");
2405        let third_event = event("session_3");
2406        let fourth_event = make_test_event(room_id, "It's a secret to everybody");
2407
2408        // Add one event in one room.
2409        self.handle_linked_chunk_updates(
2410            linked_chunk_id,
2411            vec![
2412                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2413                Update::PushItems {
2414                    at: Position::new(CId::new(0), 0),
2415                    items: vec![first_event.clone(), second_event.clone(), fourth_event.clone()],
2416                },
2417            ],
2418        )
2419        .await
2420        .unwrap();
2421
2422        // Add an event in a different room.
2423        self.handle_linked_chunk_updates(
2424            another_linked_chunk_id,
2425            vec![
2426                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2427                Update::PushItems {
2428                    at: Position::new(CId::new(0), 0),
2429                    items: vec![third_event.clone()],
2430                },
2431            ],
2432        )
2433        .await
2434        .unwrap();
2435
2436        // Now let's find all the encrypted events of the first room.
2437        let events = self
2438            .get_room_events(room_id, Some("m.room.encrypted"), None)
2439            .await
2440            .expect("failed to query for room events");
2441
2442        assert_eq!(events.len(), 2);
2443        assert_expected_events!(events, [first_event, second_event]);
2444
2445        // Now let's find all the encrypted events which were encrypted using
2446        // the first session ID.
2447        let events = self
2448            .get_room_events(room_id, Some("m.room.encrypted"), Some("session_1"))
2449            .await
2450            .expect("failed to query for room events");
2451
2452        assert_eq!(events.len(), 1);
2453        assert_expected_events!(events, [first_event]);
2454    }
2455
2456    async fn test_get_room_events_with_event_in_room_and_thread(&self) {
2457        let room_id = *DEFAULT_TEST_ROOM_ID;
2458        let thread_root = event_id!("$thread_root");
2459
2460        // Create an event that will be only be inserted into the room
2461        let room_event_id = event_id!("$room_event");
2462        let room_event = make_test_event_with_event_id(room_id, "room event", Some(room_event_id));
2463
2464        // Create an event that will only be inserted into the thread. This may
2465        // not be a sensible operation in practice, as threads seem to always
2466        // exist in a room, but let's test it anyway.
2467        let thread_event_id = event_id!("$thread_event");
2468        let thread_event =
2469            make_test_event_with_event_id(room_id, "thread event", Some(thread_event_id));
2470
2471        // Create an event that will be inserted into both the room and thread
2472        // linked chunks.
2473        let room_and_thread_event_id = event_id!("$room_and_thread");
2474        let room_and_thread_event = make_test_event_with_event_id(
2475            room_id,
2476            "room and thread",
2477            Some(room_and_thread_event_id),
2478        );
2479
2480        let room_linked_chunk_id = LinkedChunkId::Room(room_id);
2481        let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
2482
2483        // Insert the relevant events into the room's linked chunk.
2484        self.handle_linked_chunk_updates(
2485            room_linked_chunk_id,
2486            vec![
2487                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2488                Update::PushItems {
2489                    at: Position::new(CId::new(1), 0),
2490                    items: vec![room_event, room_and_thread_event.clone()],
2491                },
2492            ],
2493        )
2494        .await
2495        .unwrap();
2496
2497        // Insert the relevant events into the thread's linked chunk.
2498        self.handle_linked_chunk_updates(
2499            thread_linked_chunk_id,
2500            vec![
2501                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2502                Update::PushItems {
2503                    at: Position::new(CId::new(1), 0),
2504                    items: vec![thread_event, room_and_thread_event],
2505                },
2506            ],
2507        )
2508        .await
2509        .unwrap();
2510
2511        // Verify that all events can be retrieved and none are duplicated in
2512        // the returned list.
2513        let expected_event_ids =
2514            BTreeSet::from([room_event_id, thread_event_id, room_and_thread_event_id]);
2515        assert_matches!(self.get_room_events(room_id, None, None).await, Ok(events) => {
2516            assert_eq!(events.len(), 3);
2517            assert!(events.iter().all(|event| {
2518                expected_event_ids.contains(event.event_id().unwrap())
2519            }));
2520        });
2521    }
2522
2523    async fn test_save_event(&self) {
2524        let room_id = room_id!("!r0:matrix.org");
2525        let another_room_id = room_id!("!r1:matrix.org");
2526
2527        let event = |msg: &str| make_test_event(room_id, msg);
2528        let event_comte = event("comté");
2529        let event_gruyere = event("gruyère");
2530
2531        // Add one event in one room.
2532        self.save_event(room_id, event_comte.clone()).await.unwrap();
2533
2534        // Add another event in another room.
2535        self.save_event(another_room_id, event_gruyere.clone()).await.unwrap();
2536
2537        // Events can be found, when searched in their own rooms.
2538        let event = self
2539            .find_event(room_id, event_comte.event_id().unwrap())
2540            .await
2541            .expect("failed to query for finding an event")
2542            .expect("failed to find an event");
2543        assert_eq!(event.event_id(), event_comte.event_id());
2544
2545        let event = self
2546            .find_event(another_room_id, event_gruyere.event_id().unwrap())
2547            .await
2548            .expect("failed to query for finding an event")
2549            .expect("failed to find an event");
2550        assert_eq!(event.event_id(), event_gruyere.event_id());
2551
2552        // But they won't be returned when searching in the wrong room.
2553        assert!(
2554            self.find_event(another_room_id, event_comte.event_id().unwrap())
2555                .await
2556                .expect("failed to query for finding an event")
2557                .is_none()
2558        );
2559        assert!(
2560            self.find_event(room_id, event_gruyere.event_id().unwrap())
2561                .await
2562                .expect("failed to query for finding an event")
2563                .is_none()
2564        );
2565    }
2566
2567    async fn test_save_event_updates_event_in_room_and_thread(&self) {
2568        let room_id = *DEFAULT_TEST_ROOM_ID;
2569        let thread_root = event_id!("$thread_root");
2570
2571        // Create an event that will be inserted into both the room and thread
2572        // linked chunks.
2573        let event_id = event_id!("$event");
2574        let event = make_test_event_with_event_id(room_id, "event", Some(event_id));
2575
2576        let room_linked_chunk_id = LinkedChunkId::Room(room_id);
2577        let thread_linked_chunk_id = LinkedChunkId::Thread(room_id, thread_root);
2578
2579        // Insert the relevant events into the room's linked chunk.
2580        self.handle_linked_chunk_updates(
2581            room_linked_chunk_id,
2582            vec![
2583                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2584                Update::PushItems { at: Position::new(CId::new(1), 0), items: vec![event.clone()] },
2585            ],
2586        )
2587        .await
2588        .unwrap();
2589
2590        // Insert the relevant events into the thread's linked chunk.
2591        self.handle_linked_chunk_updates(
2592            thread_linked_chunk_id,
2593            vec![
2594                Update::NewItemsChunk { previous: None, new: CId::new(1), next: None },
2595                Update::PushItems { at: Position::new(CId::new(1), 0), items: vec![event.clone()] },
2596            ],
2597        )
2598        .await
2599        .unwrap();
2600
2601        // Save updated version of original event, which should replace the
2602        // content of the existing event
2603        let updated_content = "updated content";
2604        let updated = make_test_event_with_event_id(room_id, updated_content, Some(event_id));
2605        self.save_event(room_id, updated).await.unwrap();
2606
2607        // Load all chunks from both room and thread
2608        let room_chunks = self.load_all_chunks(room_linked_chunk_id).await.unwrap();
2609        let thread_chunks = self.load_all_chunks(thread_linked_chunk_id).await.unwrap();
2610
2611        assert_eq!(room_chunks.len(), 1);
2612        assert_eq!(thread_chunks.len(), 1);
2613
2614        // Verify the event has been updated in both room and thread
2615        assert_matches!(&room_chunks[0].content, ChunkContent::Items(events) => {
2616            assert_eq!(events.len(), 1);
2617            assert_eq!(events[0].event_id(), Some(event_id));
2618            check_test_event(&events[0], updated_content);
2619        });
2620        assert_matches!(&thread_chunks[0].content, ChunkContent::Items(events) => {
2621            assert_eq!(events.len(), 1);
2622            assert_eq!(events[0].event_id(), Some(event_id));
2623            check_test_event(&events[0], updated_content);
2624        });
2625    }
2626
2627    async fn test_thread_vs_room_linked_chunk(&self) {
2628        let room_id = room_id!("!r0:matrix.org");
2629
2630        let event = |msg: &str| make_test_event(room_id, msg);
2631
2632        let thread1_ev = event("comté");
2633        let thread2_ev = event("gruyère");
2634        let thread2_ev2 = event("beaufort");
2635        let room_ev = event("brillat savarin triple crème");
2636
2637        let thread_root1 = event("thread1");
2638        let thread_root2 = event("thread2");
2639
2640        // Add one event in a thread linked chunk.
2641        self.handle_linked_chunk_updates(
2642            LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap()),
2643            vec![
2644                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2645                Update::PushItems {
2646                    at: Position::new(CId::new(0), 0),
2647                    items: vec![thread1_ev.clone()],
2648                },
2649            ],
2650        )
2651        .await
2652        .unwrap();
2653
2654        // Add one event in another thread linked chunk (same room).
2655        self.handle_linked_chunk_updates(
2656            LinkedChunkId::Thread(room_id, thread_root2.event_id().unwrap()),
2657            vec![
2658                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2659                Update::PushItems {
2660                    at: Position::new(CId::new(0), 0),
2661                    items: vec![thread2_ev.clone(), thread2_ev2.clone()],
2662                },
2663            ],
2664        )
2665        .await
2666        .unwrap();
2667
2668        // Add another event to the room linked chunk.
2669        self.handle_linked_chunk_updates(
2670            LinkedChunkId::Room(room_id),
2671            vec![
2672                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
2673                Update::PushItems {
2674                    at: Position::new(CId::new(0), 0),
2675                    items: vec![room_ev.clone()],
2676                },
2677            ],
2678        )
2679        .await
2680        .unwrap();
2681
2682        // All the events can be found with `find_event()` for the room.
2683        self.find_event(room_id, thread2_ev.event_id().unwrap())
2684            .await
2685            .expect("failed to query for finding an event")
2686            .expect("failed to find thread1_ev");
2687
2688        self.find_event(room_id, thread2_ev.event_id().unwrap())
2689            .await
2690            .expect("failed to query for finding an event")
2691            .expect("failed to find thread2_ev");
2692
2693        self.find_event(room_id, thread2_ev2.event_id().unwrap())
2694            .await
2695            .expect("failed to query for finding an event")
2696            .expect("failed to find thread2_ev2");
2697
2698        self.find_event(room_id, room_ev.event_id().unwrap())
2699            .await
2700            .expect("failed to query for finding an event")
2701            .expect("failed to find room_ev");
2702
2703        // Finding duplicates operates based on the linked chunk id.
2704        let dups = self
2705            .filter_duplicated_events(
2706                LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap()),
2707                vec![
2708                    thread1_ev.event_id().unwrap().to_owned(),
2709                    room_ev.event_id().unwrap().to_owned(),
2710                ],
2711            )
2712            .await
2713            .unwrap();
2714        assert_eq!(dups.len(), 1);
2715        assert_eq!(dups[0].0, thread1_ev.event_id().unwrap());
2716
2717        // Loading all chunks operates based on the linked chunk id.
2718        let all_chunks = self
2719            .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root2.event_id().unwrap()))
2720            .await
2721            .unwrap();
2722        assert_eq!(all_chunks.len(), 1);
2723        assert_eq!(all_chunks[0].identifier, CId::new(0));
2724        assert_let!(ChunkContent::Items(observed_items) = all_chunks[0].content.clone());
2725        assert_eq!(observed_items.len(), 2);
2726        assert_eq!(observed_items[0].event_id(), thread2_ev.event_id());
2727        assert_eq!(observed_items[1].event_id(), thread2_ev2.event_id());
2728
2729        // Loading the metadata of all chunks operates based on the linked chunk
2730        // id.
2731        let metas = self
2732            .load_all_chunks_metadata(LinkedChunkId::Thread(
2733                room_id,
2734                thread_root2.event_id().unwrap(),
2735            ))
2736            .await
2737            .unwrap();
2738        assert_eq!(metas.len(), 1);
2739        assert_eq!(metas[0].identifier, CId::new(0));
2740        assert_eq!(metas[0].num_items, 2);
2741
2742        // Loading the last chunk operates based on the linked chunk id.
2743        let (last_chunk, _chunk_identifier_generator) = self
2744            .load_last_chunk(LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap()))
2745            .await
2746            .unwrap();
2747        let last_chunk = last_chunk.unwrap();
2748        assert_eq!(last_chunk.identifier, CId::new(0));
2749        assert_let!(ChunkContent::Items(observed_items) = last_chunk.content);
2750        assert_eq!(observed_items.len(), 1);
2751        assert_eq!(observed_items[0].event_id(), thread1_ev.event_id());
2752    }
2753}
2754
2755/// Macro building to allow your `EventCacheStore` implementation to run the
2756/// entire tests suite locally.
2757///
2758/// You need to provide a
2759/// `async fn get_event_cache_store() -> EventCacheStoreResult<impl
2760/// EventCacheStore>` providing a fresh event cache store on the same level you
2761/// invoke the macro.
2762///
2763/// ## Usage example
2764///
2765/// ```no_run
2766/// # use matrix_sdk_base::event_cache::store::{
2767/// #    EventCacheStore,
2768/// #    MemoryStore as MyStore,
2769/// #    Result as EventCacheStoreResult,
2770/// # };
2771///
2772/// #[cfg(test)]
2773/// mod tests {
2774///     use super::{EventCacheStore, EventCacheStoreResult, MyStore};
2775///
2776///     async fn get_event_cache_store()
2777///     -> EventCacheStoreResult<impl EventCacheStore> {
2778///         Ok(MyStore::new())
2779///     }
2780///
2781///     event_cache_store_integration_tests!();
2782/// }
2783/// ```
2784#[allow(unused_macros, unused_extern_crates)]
2785#[macro_export]
2786macro_rules! event_cache_store_integration_tests {
2787    () => {
2788        mod event_cache_store_integration_tests {
2789            use matrix_sdk_test::async_test;
2790            use $crate::event_cache::store::{
2791                EventCacheStoreIntegrationTests, IntoEventCacheStore,
2792            };
2793
2794            use super::get_event_cache_store;
2795
2796            #[async_test]
2797            async fn test_handle_updates_and_rebuild_linked_chunk() {
2798                let event_cache_store =
2799                    get_event_cache_store().await.unwrap().into_event_cache_store();
2800                event_cache_store.test_handle_updates_and_rebuild_linked_chunk().await;
2801            }
2802
2803            #[async_test]
2804            async fn test_linked_chunk_exists_before_referenced() {
2805                let event_cache_store =
2806                    get_event_cache_store().await.unwrap().into_event_cache_store();
2807                event_cache_store.test_linked_chunk_exists_before_referenced().await;
2808            }
2809
2810            #[async_test]
2811            async fn test_linked_chunk_allow_same_event_in_room_and_thread() {
2812                let event_cache_store =
2813                    get_event_cache_store().await.unwrap().into_event_cache_store();
2814                event_cache_store.test_linked_chunk_allows_same_event_in_room_and_thread().await;
2815            }
2816
2817            #[async_test]
2818            async fn test_load_last_chunk() {
2819                let event_cache_store =
2820                    get_event_cache_store().await.unwrap().into_event_cache_store();
2821                event_cache_store.test_load_last_chunk().await;
2822            }
2823
2824            #[async_test]
2825            async fn test_load_last_chunk_with_a_cycle() {
2826                let event_cache_store =
2827                    get_event_cache_store().await.unwrap().into_event_cache_store();
2828                event_cache_store.test_load_last_chunk_with_a_cycle().await;
2829            }
2830
2831            #[async_test]
2832            async fn test_load_previous_chunk() {
2833                let event_cache_store =
2834                    get_event_cache_store().await.unwrap().into_event_cache_store();
2835                event_cache_store.test_load_previous_chunk().await;
2836            }
2837
2838            #[async_test]
2839            async fn test_linked_chunk_incremental_loading() {
2840                let event_cache_store =
2841                    get_event_cache_store().await.unwrap().into_event_cache_store();
2842                event_cache_store.test_linked_chunk_incremental_loading().await;
2843            }
2844
2845            #[async_test]
2846            async fn test_linked_chunk_remove_chunk() {
2847                let event_cache_store =
2848                    get_event_cache_store().await.unwrap().into_event_cache_store();
2849                event_cache_store.test_linked_chunk_remove_chunk().await;
2850            }
2851
2852            #[async_test]
2853            async fn test_linked_chunk_push_items() {
2854                let event_cache_store =
2855                    get_event_cache_store().await.unwrap().into_event_cache_store();
2856                event_cache_store.test_linked_chunk_push_items().await;
2857            }
2858
2859            #[async_test]
2860            async fn test_linked_chunk_replace_item() {
2861                let event_cache_store =
2862                    get_event_cache_store().await.unwrap().into_event_cache_store();
2863                event_cache_store.test_linked_chunk_replace_item().await;
2864            }
2865
2866            #[async_test]
2867            async fn test_linked_chunk_filter_incomplete_events() {
2868                let event_cache_store =
2869                    get_event_cache_store().await.unwrap().into_event_cache_store();
2870                event_cache_store.test_linked_chunk_filter_incomplete_events().await;
2871            }
2872
2873            #[async_test]
2874            async fn test_linked_chunk_remove_item() {
2875                let event_cache_store =
2876                    get_event_cache_store().await.unwrap().into_event_cache_store();
2877                event_cache_store.test_linked_chunk_remove_item().await;
2878            }
2879
2880            #[async_test]
2881            async fn test_linked_chunk_detach_last_items() {
2882                let event_cache_store =
2883                    get_event_cache_store().await.unwrap().into_event_cache_store();
2884                event_cache_store.test_linked_chunk_detach_last_items().await;
2885            }
2886
2887            #[async_test]
2888            async fn test_linked_chunk_start_end_reattach_items() {
2889                let event_cache_store =
2890                    get_event_cache_store().await.unwrap().into_event_cache_store();
2891                event_cache_store.test_linked_chunk_start_end_reattach_items().await;
2892            }
2893
2894            #[async_test]
2895            async fn test_linked_chunk_clear() {
2896                let event_cache_store =
2897                    get_event_cache_store().await.unwrap().into_event_cache_store();
2898                event_cache_store.test_linked_chunk_clear().await;
2899            }
2900
2901            #[async_test]
2902            async fn test_linked_chunk_clear_and_reinsert() {
2903                let event_cache_store =
2904                    get_event_cache_store().await.unwrap().into_event_cache_store();
2905                event_cache_store.test_linked_chunk_clear_and_reinsert().await;
2906            }
2907
2908            #[async_test]
2909            async fn test_rebuild_empty_linked_chunk() {
2910                let event_cache_store =
2911                    get_event_cache_store().await.unwrap().into_event_cache_store();
2912                event_cache_store.test_rebuild_empty_linked_chunk().await;
2913            }
2914
2915            #[async_test]
2916            async fn test_linked_chunk_multiple_rooms() {
2917                let event_cache_store =
2918                    get_event_cache_store().await.unwrap().into_event_cache_store();
2919                event_cache_store.test_linked_chunk_multiple_rooms().await;
2920            }
2921
2922            #[async_test]
2923            async fn test_load_all_chunks_metadata() {
2924                let event_cache_store =
2925                    get_event_cache_store().await.unwrap().into_event_cache_store();
2926                event_cache_store.test_load_all_chunks_metadata().await;
2927            }
2928
2929            #[async_test]
2930            async fn test_load_and_update_thread_info() {
2931                let event_cache_store =
2932                    get_event_cache_store().await.unwrap().into_event_cache_store();
2933                event_cache_store.test_load_and_update_thread_info().await;
2934            }
2935
2936            #[async_test]
2937            async fn test_clear_all_events() {
2938                let event_cache_store =
2939                    get_event_cache_store().await.unwrap().into_event_cache_store();
2940                event_cache_store.test_clear_all_events().await;
2941            }
2942
2943            #[async_test]
2944            async fn test_clear_all_events_for_specific_room() {
2945                let event_cache_store =
2946                    get_event_cache_store().await.unwrap().into_event_cache_store();
2947                event_cache_store.test_clear_all_events_for_specific_room().await;
2948            }
2949
2950            #[async_test]
2951            async fn test_filter_duplicated_events() {
2952                let event_cache_store =
2953                    get_event_cache_store().await.unwrap().into_event_cache_store();
2954                event_cache_store.test_filter_duplicated_events().await;
2955            }
2956
2957            #[async_test]
2958            async fn test_filter_duplicate_events_no_events() {
2959                let event_cache_store =
2960                    get_event_cache_store().await.unwrap().into_event_cache_store();
2961                event_cache_store.test_filter_duplicate_events_no_events().await;
2962            }
2963
2964            #[async_test]
2965            async fn test_find_event() {
2966                let event_cache_store =
2967                    get_event_cache_store().await.unwrap().into_event_cache_store();
2968                event_cache_store.test_find_event().await;
2969            }
2970
2971            #[async_test]
2972            async fn test_find_event_when_event_in_room_and_thread() {
2973                let event_cache_store =
2974                    get_event_cache_store().await.unwrap().into_event_cache_store();
2975                event_cache_store.test_find_event_when_event_in_room_and_thread().await;
2976            }
2977
2978            #[async_test]
2979            async fn test_find_event_relations() {
2980                let event_cache_store =
2981                    get_event_cache_store().await.unwrap().into_event_cache_store();
2982                event_cache_store.test_find_event_relations().await;
2983            }
2984
2985            #[async_test]
2986            async fn test_find_event_relations_when_event_in_room_and_thread() {
2987                let event_cache_store =
2988                    get_event_cache_store().await.unwrap().into_event_cache_store();
2989                event_cache_store.test_find_event_relations_when_event_in_room_and_thread().await;
2990            }
2991
2992            #[async_test]
2993            async fn test_get_room_events() {
2994                let event_cache_store =
2995                    get_event_cache_store().await.unwrap().into_event_cache_store();
2996                event_cache_store.test_get_room_events().await;
2997            }
2998
2999            #[async_test]
3000            async fn test_get_room_events_filtered() {
3001                let event_cache_store =
3002                    get_event_cache_store().await.unwrap().into_event_cache_store();
3003                event_cache_store.test_get_room_events_filtered().await;
3004            }
3005
3006            #[async_test]
3007            async fn test_get_room_events_with_event_in_room_and_thread() {
3008                let event_cache_store =
3009                    get_event_cache_store().await.unwrap().into_event_cache_store();
3010                event_cache_store.test_get_room_events_with_event_in_room_and_thread().await;
3011            }
3012
3013            #[async_test]
3014            async fn test_save_event() {
3015                let event_cache_store =
3016                    get_event_cache_store().await.unwrap().into_event_cache_store();
3017                event_cache_store.test_save_event().await;
3018            }
3019
3020            #[async_test]
3021            async fn test_save_event_updates_event_in_room_and_thread() {
3022                let event_cache_store =
3023                    get_event_cache_store().await.unwrap().into_event_cache_store();
3024                event_cache_store.test_save_event_updates_event_in_room_and_thread().await;
3025            }
3026
3027            #[async_test]
3028            async fn test_thread_vs_room_linked_chunk() {
3029                let event_cache_store =
3030                    get_event_cache_store().await.unwrap().into_event_cache_store();
3031                event_cache_store.test_thread_vs_room_linked_chunk().await;
3032            }
3033        }
3034    };
3035}
3036
3037/// Macro generating tests for the event cache store, related to time (mostly
3038/// for the cross-process lock).
3039#[allow(unused_macros)]
3040#[macro_export]
3041macro_rules! event_cache_store_integration_tests_time {
3042    () => {
3043        mod event_cache_store_integration_tests_time {
3044            use std::time::Duration;
3045
3046            #[cfg(all(target_family = "wasm", target_os = "unknown"))]
3047            use gloo_timers::future::sleep;
3048            use matrix_sdk_test::async_test;
3049            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3050            use tokio::time::sleep;
3051            use $crate::event_cache::store::IntoEventCacheStore;
3052
3053            use super::get_event_cache_store;
3054
3055            #[async_test]
3056            async fn test_lease_locks() {
3057                let store = get_event_cache_store().await.unwrap().into_event_cache_store();
3058
3059                let acquired0 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
3060                assert_eq!(acquired0, Some(1)); // first lock generation
3061
3062                // Should extend the lease automatically (same holder).
3063                let acquired2 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
3064                assert_eq!(acquired2, Some(1)); // same lock generation
3065
3066                // Should extend the lease automatically (same holder + time is
3067                // ok).
3068                let acquired3 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
3069                assert_eq!(acquired3, Some(1)); // same lock generation
3070
3071                // Another attempt at taking the lock should fail, because it's
3072                // taken.
3073                let acquired4 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
3074                assert!(acquired4.is_none()); // not acquired
3075
3076                // Even if we insist.
3077                let acquired5 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
3078                assert!(acquired5.is_none()); // not acquired
3079
3080                // That's a nice test we got here, go take a little nap.
3081                sleep(Duration::from_millis(50)).await;
3082
3083                // Still too early.
3084                let acquired55 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
3085                assert!(acquired55.is_none()); // not acquired
3086
3087                // Ok you can take another nap then.
3088                sleep(Duration::from_millis(250)).await;
3089
3090                // At some point, we do get the lock.
3091                let acquired6 = store.try_take_leased_lock(0, "key", "bob").await.unwrap();
3092                assert_eq!(acquired6, Some(2)); // new lock generation!
3093
3094                sleep(Duration::from_millis(1)).await;
3095
3096                // The other gets it almost immediately too.
3097                let acquired7 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
3098                assert_eq!(acquired7, Some(3)); // new lock generation!
3099
3100                sleep(Duration::from_millis(1)).await;
3101
3102                // But when we take a longer lease…
3103                let acquired8 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
3104                assert_eq!(acquired8, Some(4)); // new lock generation!
3105
3106                // It blocks the other user.
3107                let acquired9 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
3108                assert!(acquired9.is_none()); // not acquired
3109
3110                // We can hold onto our lease.
3111                let acquired10 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
3112                assert_eq!(acquired10, Some(4)); // same lock generation
3113            }
3114        }
3115    };
3116}