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