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::{collections::BTreeMap, sync::Arc};
18
19use assert_matches::assert_matches;
20use assert_matches2::assert_let;
21use matrix_sdk_common::{
22    deserialized_responses::{
23        AlgorithmInfo, DecryptedRoomEvent, EncryptionInfo, TimelineEvent, TimelineEventKind,
24        VerificationState,
25    },
26    linked_chunk::{
27        ChunkContent, ChunkIdentifier as CId, LinkedChunkId, Position, Update, lazy_loader,
28    },
29};
30use matrix_sdk_test::{ALICE, DEFAULT_TEST_ROOM_ID, event_factory::EventFactory};
31use ruma::{
32    EventId, RoomId, event_id,
33    events::{
34        AnyMessageLikeEvent, AnyTimelineEvent, relation::RelationType,
35        room::message::RoomMessageEventContentWithoutRelation,
36    },
37    push::Action,
38    room_id,
39};
40
41use super::DynEventCacheStore;
42use crate::event_cache::{Gap, store::DEFAULT_CHUNK_CAPACITY};
43
44/// Create a test event with all data filled, for testing that linked chunk
45/// correctly stores event data.
46///
47/// Keep in sync with [`check_test_event`].
48pub fn make_test_event(room_id: &RoomId, content: &str) -> TimelineEvent {
49    make_test_event_with_event_id(room_id, content, None)
50}
51
52/// Same as [`make_test_event`], with an extra event id.
53pub fn make_test_event_with_event_id(
54    room_id: &RoomId,
55    content: &str,
56    event_id: Option<&EventId>,
57) -> TimelineEvent {
58    let encryption_info = Arc::new(EncryptionInfo {
59        sender: (*ALICE).into(),
60        sender_device: None,
61        algorithm_info: AlgorithmInfo::MegolmV1AesSha2 {
62            curve25519_key: "1337".to_owned(),
63            sender_claimed_keys: Default::default(),
64            session_id: Some("mysessionid9".to_owned()),
65        },
66        verification_state: VerificationState::Verified,
67    });
68
69    let mut builder = EventFactory::new().text_msg(content).room(room_id).sender(*ALICE);
70    if let Some(event_id) = event_id {
71        builder = builder.event_id(event_id);
72    }
73    let event = builder.into_raw();
74
75    TimelineEvent::from_decrypted(
76        DecryptedRoomEvent { event, encryption_info, unsigned_encryption_info: None },
77        Some(vec![Action::Notify]),
78    )
79}
80
81/// Check that an event created with [`make_test_event`] contains the expected
82/// data.
83///
84/// Keep in sync with [`make_test_event`].
85#[track_caller]
86pub fn check_test_event(event: &TimelineEvent, text: &str) {
87    // Check push actions.
88    let actions = event.push_actions().unwrap();
89    assert_eq!(actions.len(), 1);
90    assert_matches!(&actions[0], Action::Notify);
91
92    // Check content.
93    assert_matches!(&event.kind, TimelineEventKind::Decrypted(d) => {
94        // Check encryption fields.
95        assert_eq!(d.encryption_info.sender, *ALICE);
96        assert_matches!(&d.encryption_info.algorithm_info, AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => {
97            assert_eq!(curve25519_key, "1337");
98        });
99
100        // Check event.
101        let deserialized = d.event.deserialize().unwrap();
102        assert_matches!(deserialized, AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::RoomMessage(msg)) => {
103            assert_eq!(msg.as_original().unwrap().content.body(), text);
104        });
105    });
106}
107
108/// `EventCacheStore` integration tests.
109///
110/// This trait is not meant to be used directly, but will be used with the
111/// `event_cache_store_integration_tests!` macro.
112#[allow(async_fn_in_trait)]
113pub trait EventCacheStoreIntegrationTests {
114    /// Test handling updates to a linked chunk and reloading these updates from
115    /// the store.
116    async fn test_handle_updates_and_rebuild_linked_chunk(&self);
117
118    /// Test loading a linked chunk incrementally (chunk by chunk) from the
119    /// store.
120    async fn test_linked_chunk_incremental_loading(&self);
121
122    /// Test that rebuilding a linked chunk from an empty store doesn't return
123    /// anything.
124    async fn test_rebuild_empty_linked_chunk(&self);
125
126    /// Test that loading a linked chunk's metadata works as intended.
127    async fn test_load_all_chunks_metadata(&self);
128
129    /// Test that clear all the rooms' linked chunks works.
130    async fn test_clear_all_linked_chunks(&self);
131
132    /// Test that removing a room from storage empties all associated data.
133    async fn test_remove_room(&self);
134
135    /// Test that filtering duplicated events works as expected.
136    async fn test_filter_duplicated_events(&self);
137
138    /// Test that an event can be found or not.
139    async fn test_find_event(&self);
140
141    /// Test that finding event relations works as expected.
142    async fn test_find_event_relations(&self);
143
144    /// Test that getting all events in a room works as expected.
145    async fn test_get_room_events(&self);
146
147    /// Test that saving an event works as expected.
148    async fn test_save_event(&self);
149
150    /// Test multiple things related to distinguishing a thread linked chunk
151    /// from a room linked chunk.
152    async fn test_thread_vs_room_linked_chunk(&self);
153}
154
155impl EventCacheStoreIntegrationTests for DynEventCacheStore {
156    async fn test_handle_updates_and_rebuild_linked_chunk(&self) {
157        let room_id = room_id!("!r0:matrix.org");
158        let linked_chunk_id = LinkedChunkId::Room(room_id);
159
160        self.handle_linked_chunk_updates(
161            linked_chunk_id,
162            vec![
163                // new chunk
164                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
165                // new items on 0
166                Update::PushItems {
167                    at: Position::new(CId::new(0), 0),
168                    items: vec![
169                        make_test_event(room_id, "hello"),
170                        make_test_event(room_id, "world"),
171                    ],
172                },
173                // a gap chunk
174                Update::NewGapChunk {
175                    previous: Some(CId::new(0)),
176                    new: CId::new(1),
177                    next: None,
178                    gap: Gap { prev_token: "parmesan".to_owned() },
179                },
180                // another items chunk
181                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
182                // new items on 2
183                Update::PushItems {
184                    at: Position::new(CId::new(2), 0),
185                    items: vec![make_test_event(room_id, "sup")],
186                },
187            ],
188        )
189        .await
190        .unwrap();
191
192        // The linked chunk is correctly reloaded.
193        let lc = lazy_loader::from_all_chunks::<3, _, _>(
194            self.load_all_chunks(linked_chunk_id).await.unwrap(),
195        )
196        .unwrap()
197        .unwrap();
198
199        let mut chunks = lc.chunks();
200
201        {
202            let first = chunks.next().unwrap();
203            // Note: we can't assert the previous/next chunks, as these fields and their
204            // getters are private.
205            assert_eq!(first.identifier(), CId::new(0));
206
207            assert_matches!(first.content(), ChunkContent::Items(events) => {
208                assert_eq!(events.len(), 2);
209                check_test_event(&events[0], "hello");
210                check_test_event(&events[1], "world");
211            });
212        }
213
214        {
215            let second = chunks.next().unwrap();
216            assert_eq!(second.identifier(), CId::new(1));
217
218            assert_matches!(second.content(), ChunkContent::Gap(gap) => {
219                assert_eq!(gap.prev_token, "parmesan");
220            });
221        }
222
223        {
224            let third = chunks.next().unwrap();
225            assert_eq!(third.identifier(), CId::new(2));
226
227            assert_matches!(third.content(), ChunkContent::Items(events) => {
228                assert_eq!(events.len(), 1);
229                check_test_event(&events[0], "sup");
230            });
231        }
232
233        assert!(chunks.next().is_none());
234    }
235
236    async fn test_load_all_chunks_metadata(&self) {
237        let room_id = room_id!("!r0:matrix.org");
238        let linked_chunk_id = LinkedChunkId::Room(room_id);
239
240        self.handle_linked_chunk_updates(
241            linked_chunk_id,
242            vec![
243                // new chunk
244                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
245                // new items on 0
246                Update::PushItems {
247                    at: Position::new(CId::new(0), 0),
248                    items: vec![
249                        make_test_event(room_id, "hello"),
250                        make_test_event(room_id, "world"),
251                    ],
252                },
253                // a gap chunk
254                Update::NewGapChunk {
255                    previous: Some(CId::new(0)),
256                    new: CId::new(1),
257                    next: None,
258                    gap: Gap { prev_token: "parmesan".to_owned() },
259                },
260                // another items chunk
261                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
262                // new items on 2
263                Update::PushItems {
264                    at: Position::new(CId::new(2), 0),
265                    items: vec![make_test_event(room_id, "sup")],
266                },
267                // and an empty items chunk to finish
268                Update::NewItemsChunk { previous: Some(CId::new(2)), new: CId::new(3), next: None },
269            ],
270        )
271        .await
272        .unwrap();
273
274        let metas = self.load_all_chunks_metadata(linked_chunk_id).await.unwrap();
275        assert_eq!(metas.len(), 4);
276
277        // The first chunk has two items.
278        assert_eq!(metas[0].identifier, CId::new(0));
279        assert_eq!(metas[0].previous, None);
280        assert_eq!(metas[0].next, Some(CId::new(1)));
281        assert_eq!(metas[0].num_items, 2);
282
283        // The second chunk is a gap, so it has 0 items.
284        assert_eq!(metas[1].identifier, CId::new(1));
285        assert_eq!(metas[1].previous, Some(CId::new(0)));
286        assert_eq!(metas[1].next, Some(CId::new(2)));
287        assert_eq!(metas[1].num_items, 0);
288
289        // The third event chunk has one item.
290        assert_eq!(metas[2].identifier, CId::new(2));
291        assert_eq!(metas[2].previous, Some(CId::new(1)));
292        assert_eq!(metas[2].next, Some(CId::new(3)));
293        assert_eq!(metas[2].num_items, 1);
294
295        // The final event chunk is empty.
296        assert_eq!(metas[3].identifier, CId::new(3));
297        assert_eq!(metas[3].previous, Some(CId::new(2)));
298        assert_eq!(metas[3].next, None);
299        assert_eq!(metas[3].num_items, 0);
300    }
301
302    async fn test_linked_chunk_incremental_loading(&self) {
303        let room_id = room_id!("!r0:matrix.org");
304        let linked_chunk_id = LinkedChunkId::Room(room_id);
305        let event = |msg: &str| make_test_event(room_id, msg);
306
307        // Load the last chunk, but none exists yet.
308        {
309            let (last_chunk, chunk_identifier_generator) =
310                self.load_last_chunk(linked_chunk_id).await.unwrap();
311
312            assert!(last_chunk.is_none());
313            assert_eq!(chunk_identifier_generator.current(), 0);
314        }
315
316        self.handle_linked_chunk_updates(
317            linked_chunk_id,
318            vec![
319                // new chunk for items
320                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
321                // new items on 0
322                Update::PushItems {
323                    at: Position::new(CId::new(0), 0),
324                    items: vec![event("a"), event("b")],
325                },
326                // new chunk for a gap
327                Update::NewGapChunk {
328                    previous: Some(CId::new(0)),
329                    new: CId::new(1),
330                    next: None,
331                    gap: Gap { prev_token: "morbier".to_owned() },
332                },
333                // new chunk for items
334                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
335                // new items on 2
336                Update::PushItems {
337                    at: Position::new(CId::new(2), 0),
338                    items: vec![event("c"), event("d"), event("e")],
339                },
340            ],
341        )
342        .await
343        .unwrap();
344
345        // Load the last chunk.
346        let mut linked_chunk = {
347            let (last_chunk, chunk_identifier_generator) =
348                self.load_last_chunk(linked_chunk_id).await.unwrap();
349
350            assert_eq!(chunk_identifier_generator.current(), 2);
351
352            let linked_chunk = lazy_loader::from_last_chunk::<DEFAULT_CHUNK_CAPACITY, _, _>(
353                last_chunk,
354                chunk_identifier_generator,
355            )
356            .unwrap() // unwrap the `Result`
357            .unwrap(); // unwrap the `Option`
358
359            let mut rchunks = linked_chunk.rchunks();
360
361            // A unique chunk.
362            assert_matches!(rchunks.next(), Some(chunk) => {
363                assert_eq!(chunk.identifier(), 2);
364                assert_eq!(chunk.lazy_previous(), Some(CId::new(1)));
365
366                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
367                    assert_eq!(events.len(), 3);
368                    check_test_event(&events[0], "c");
369                    check_test_event(&events[1], "d");
370                    check_test_event(&events[2], "e");
371                });
372            });
373
374            assert!(rchunks.next().is_none());
375
376            linked_chunk
377        };
378
379        // Load the previous chunk: this is a gap.
380        {
381            let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
382            let previous_chunk =
383                self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap().unwrap();
384
385            lazy_loader::insert_new_first_chunk(&mut linked_chunk, previous_chunk).unwrap();
386
387            let mut rchunks = linked_chunk.rchunks();
388
389            // The last chunk.
390            assert_matches!(rchunks.next(), Some(chunk) => {
391                assert_eq!(chunk.identifier(), 2);
392                assert!(chunk.lazy_previous().is_none());
393
394                // Already asserted, but let's be sure nothing breaks.
395                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
396                    assert_eq!(events.len(), 3);
397                    check_test_event(&events[0], "c");
398                    check_test_event(&events[1], "d");
399                    check_test_event(&events[2], "e");
400                });
401            });
402
403            // The new chunk.
404            assert_matches!(rchunks.next(), Some(chunk) => {
405                assert_eq!(chunk.identifier(), 1);
406                assert_eq!(chunk.lazy_previous(), Some(CId::new(0)));
407
408                assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
409                    assert_eq!(gap.prev_token, "morbier");
410                });
411            });
412
413            assert!(rchunks.next().is_none());
414        }
415
416        // Load the previous chunk: these are items.
417        {
418            let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
419            let previous_chunk =
420                self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap().unwrap();
421
422            lazy_loader::insert_new_first_chunk(&mut linked_chunk, previous_chunk).unwrap();
423
424            let mut rchunks = linked_chunk.rchunks();
425
426            // The last chunk.
427            assert_matches!(rchunks.next(), Some(chunk) => {
428                assert_eq!(chunk.identifier(), 2);
429                assert!(chunk.lazy_previous().is_none());
430
431                // Already asserted, but let's be sure nothing breaks.
432                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
433                    assert_eq!(events.len(), 3);
434                    check_test_event(&events[0], "c");
435                    check_test_event(&events[1], "d");
436                    check_test_event(&events[2], "e");
437                });
438            });
439
440            // Its previous chunk.
441            assert_matches!(rchunks.next(), Some(chunk) => {
442                assert_eq!(chunk.identifier(), 1);
443                assert!(chunk.lazy_previous().is_none());
444
445                // Already asserted, but let's be sure nothing breaks.
446                assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
447                    assert_eq!(gap.prev_token, "morbier");
448                });
449            });
450
451            // The new chunk.
452            assert_matches!(rchunks.next(), Some(chunk) => {
453                assert_eq!(chunk.identifier(), 0);
454                assert!(chunk.lazy_previous().is_none());
455
456                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
457                    assert_eq!(events.len(), 2);
458                    check_test_event(&events[0], "a");
459                    check_test_event(&events[1], "b");
460                });
461            });
462
463            assert!(rchunks.next().is_none());
464        }
465
466        // Load the previous chunk: there is none.
467        {
468            let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
469            let previous_chunk =
470                self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap();
471
472            assert!(previous_chunk.is_none());
473        }
474
475        // One last check: a round of assert by using the forwards chunk iterator
476        // instead of the backwards chunk iterator.
477        {
478            let mut chunks = linked_chunk.chunks();
479
480            // The first chunk.
481            assert_matches!(chunks.next(), Some(chunk) => {
482                assert_eq!(chunk.identifier(), 0);
483                assert!(chunk.lazy_previous().is_none());
484
485                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
486                    assert_eq!(events.len(), 2);
487                    check_test_event(&events[0], "a");
488                    check_test_event(&events[1], "b");
489                });
490            });
491
492            // The second chunk.
493            assert_matches!(chunks.next(), Some(chunk) => {
494                assert_eq!(chunk.identifier(), 1);
495                assert!(chunk.lazy_previous().is_none());
496
497                assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
498                    assert_eq!(gap.prev_token, "morbier");
499                });
500            });
501
502            // The third and last chunk.
503            assert_matches!(chunks.next(), Some(chunk) => {
504                assert_eq!(chunk.identifier(), 2);
505                assert!(chunk.lazy_previous().is_none());
506
507                assert_matches!(chunk.content(), ChunkContent::Items(events) => {
508                    assert_eq!(events.len(), 3);
509                    check_test_event(&events[0], "c");
510                    check_test_event(&events[1], "d");
511                    check_test_event(&events[2], "e");
512                });
513            });
514
515            assert!(chunks.next().is_none());
516        }
517    }
518
519    async fn test_rebuild_empty_linked_chunk(&self) {
520        // When I rebuild a linked chunk from an empty store, it's empty.
521        let linked_chunk = lazy_loader::from_all_chunks::<3, _, _>(
522            self.load_all_chunks(LinkedChunkId::Room(&DEFAULT_TEST_ROOM_ID)).await.unwrap(),
523        )
524        .unwrap();
525        assert!(linked_chunk.is_none());
526    }
527
528    async fn test_clear_all_linked_chunks(&self) {
529        let r0 = room_id!("!r0:matrix.org");
530        let linked_chunk_id0 = LinkedChunkId::Room(r0);
531        let r1 = room_id!("!r1:matrix.org");
532        let linked_chunk_id1 = LinkedChunkId::Room(r1);
533
534        // Add updates for the first room.
535        self.handle_linked_chunk_updates(
536            linked_chunk_id0,
537            vec![
538                // new chunk
539                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
540                // new items on 0
541                Update::PushItems {
542                    at: Position::new(CId::new(0), 0),
543                    items: vec![make_test_event(r0, "hello"), make_test_event(r0, "world")],
544                },
545            ],
546        )
547        .await
548        .unwrap();
549
550        // Add updates for the second room.
551        self.handle_linked_chunk_updates(
552            linked_chunk_id1,
553            vec![
554                // Empty items chunk.
555                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
556                // a gap chunk
557                Update::NewGapChunk {
558                    previous: Some(CId::new(0)),
559                    new: CId::new(1),
560                    next: None,
561                    gap: Gap { prev_token: "bleu d'auvergne".to_owned() },
562                },
563                // another items chunk
564                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
565                // new items on 0
566                Update::PushItems {
567                    at: Position::new(CId::new(2), 0),
568                    items: vec![make_test_event(r0, "yummy")],
569                },
570            ],
571        )
572        .await
573        .unwrap();
574
575        // Sanity check: both linked chunks can be reloaded.
576        assert!(
577            lazy_loader::from_all_chunks::<3, _, _>(
578                self.load_all_chunks(linked_chunk_id0).await.unwrap()
579            )
580            .unwrap()
581            .is_some()
582        );
583        assert!(
584            lazy_loader::from_all_chunks::<3, _, _>(
585                self.load_all_chunks(linked_chunk_id1).await.unwrap()
586            )
587            .unwrap()
588            .is_some()
589        );
590
591        // Clear the chunks.
592        self.clear_all_linked_chunks().await.unwrap();
593
594        // Both rooms now have no linked chunk.
595        assert!(
596            lazy_loader::from_all_chunks::<3, _, _>(
597                self.load_all_chunks(linked_chunk_id0).await.unwrap()
598            )
599            .unwrap()
600            .is_none()
601        );
602        assert!(
603            lazy_loader::from_all_chunks::<3, _, _>(
604                self.load_all_chunks(linked_chunk_id1).await.unwrap()
605            )
606            .unwrap()
607            .is_none()
608        );
609    }
610
611    async fn test_remove_room(&self) {
612        let r0 = room_id!("!r0:matrix.org");
613        let linked_chunk_id0 = LinkedChunkId::Room(r0);
614        let r1 = room_id!("!r1:matrix.org");
615        let linked_chunk_id1 = LinkedChunkId::Room(r1);
616
617        // Add updates to the first room.
618        self.handle_linked_chunk_updates(
619            linked_chunk_id0,
620            vec![
621                // new chunk
622                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
623                // new items on 0
624                Update::PushItems {
625                    at: Position::new(CId::new(0), 0),
626                    items: vec![make_test_event(r0, "hello"), make_test_event(r0, "world")],
627                },
628            ],
629        )
630        .await
631        .unwrap();
632
633        // Add updates to the second room.
634        self.handle_linked_chunk_updates(
635            linked_chunk_id1,
636            vec![
637                // new chunk
638                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
639                // new items on 0
640                Update::PushItems {
641                    at: Position::new(CId::new(0), 0),
642                    items: vec![make_test_event(r0, "yummy")],
643                },
644            ],
645        )
646        .await
647        .unwrap();
648
649        // Try to remove content from r0.
650        self.remove_room(r0).await.unwrap();
651
652        // Check that r0 doesn't have a linked chunk anymore.
653        let r0_linked_chunk = self.load_all_chunks(linked_chunk_id0).await.unwrap();
654        assert!(r0_linked_chunk.is_empty());
655
656        // Check that r1 is unaffected.
657        let r1_linked_chunk = self.load_all_chunks(linked_chunk_id1).await.unwrap();
658        assert!(!r1_linked_chunk.is_empty());
659    }
660
661    async fn test_filter_duplicated_events(&self) {
662        let room_id = room_id!("!r0:matrix.org");
663        let linked_chunk_id = LinkedChunkId::Room(room_id);
664        let another_room_id = room_id!("!r1:matrix.org");
665        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
666        let event = |msg: &str| make_test_event(room_id, msg);
667
668        let event_comte = event("comté");
669        let event_brigand = event("brigand du jorat");
670        let event_raclette = event("raclette");
671        let event_morbier = event("morbier");
672        let event_gruyere = event("gruyère");
673        let event_tome = event("tome");
674        let event_mont_dor = event("mont d'or");
675
676        self.handle_linked_chunk_updates(
677            linked_chunk_id,
678            vec![
679                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
680                Update::PushItems {
681                    at: Position::new(CId::new(0), 0),
682                    items: vec![event_comte.clone(), event_brigand.clone()],
683                },
684                Update::NewGapChunk {
685                    previous: Some(CId::new(0)),
686                    new: CId::new(1),
687                    next: None,
688                    gap: Gap { prev_token: "brillat-savarin".to_owned() },
689                },
690                Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
691                Update::PushItems {
692                    at: Position::new(CId::new(2), 0),
693                    items: vec![event_morbier.clone(), event_mont_dor.clone()],
694                },
695            ],
696        )
697        .await
698        .unwrap();
699
700        // Add other events in another room, to ensure filtering take the `room_id` into
701        // account.
702        self.handle_linked_chunk_updates(
703            another_linked_chunk_id,
704            vec![
705                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
706                Update::PushItems {
707                    at: Position::new(CId::new(0), 0),
708                    items: vec![event_tome.clone()],
709                },
710            ],
711        )
712        .await
713        .unwrap();
714
715        let duplicated_events = BTreeMap::from_iter(
716            self.filter_duplicated_events(
717                linked_chunk_id,
718                vec![
719                    event_comte.event_id().unwrap().to_owned(),
720                    event_raclette.event_id().unwrap().to_owned(),
721                    event_morbier.event_id().unwrap().to_owned(),
722                    event_gruyere.event_id().unwrap().to_owned(),
723                    event_tome.event_id().unwrap().to_owned(),
724                    event_mont_dor.event_id().unwrap().to_owned(),
725                ],
726            )
727            .await
728            .unwrap(),
729        );
730
731        assert_eq!(duplicated_events.len(), 3);
732
733        assert_eq!(
734            *duplicated_events.get(&event_comte.event_id().unwrap()).unwrap(),
735            Position::new(CId::new(0), 0)
736        );
737        assert_eq!(
738            *duplicated_events.get(&event_morbier.event_id().unwrap()).unwrap(),
739            Position::new(CId::new(2), 0)
740        );
741        assert_eq!(
742            *duplicated_events.get(&event_mont_dor.event_id().unwrap()).unwrap(),
743            Position::new(CId::new(2), 1)
744        );
745    }
746
747    async fn test_find_event(&self) {
748        let room_id = room_id!("!r0:matrix.org");
749        let another_room_id = room_id!("!r1:matrix.org");
750        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
751        let event = |msg: &str| make_test_event(room_id, msg);
752
753        let event_comte = event("comté");
754        let event_gruyere = event("gruyère");
755
756        // Add one event in one room.
757        self.handle_linked_chunk_updates(
758            LinkedChunkId::Room(room_id),
759            vec![
760                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
761                Update::PushItems {
762                    at: Position::new(CId::new(0), 0),
763                    items: vec![event_comte.clone()],
764                },
765            ],
766        )
767        .await
768        .unwrap();
769
770        // Add another event in another room.
771        self.handle_linked_chunk_updates(
772            another_linked_chunk_id,
773            vec![
774                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
775                Update::PushItems {
776                    at: Position::new(CId::new(0), 0),
777                    items: vec![event_gruyere.clone()],
778                },
779            ],
780        )
781        .await
782        .unwrap();
783
784        // Now let's find the event.
785        let event = self
786            .find_event(room_id, event_comte.event_id().unwrap().as_ref())
787            .await
788            .expect("failed to query for finding an event")
789            .expect("failed to find an event");
790
791        assert_eq!(event.event_id(), event_comte.event_id());
792
793        // Now let's try to find an event that exists, but not in the expected room.
794        assert!(
795            self.find_event(room_id, event_gruyere.event_id().unwrap().as_ref())
796                .await
797                .expect("failed to query for finding an event")
798                .is_none()
799        );
800
801        // Clearing the rooms also clears the event's storage.
802        self.clear_all_linked_chunks().await.expect("failed to clear all rooms chunks");
803        assert!(
804            self.find_event(room_id, event_comte.event_id().unwrap().as_ref())
805                .await
806                .expect("failed to query for finding an event")
807                .is_none()
808        );
809    }
810
811    async fn test_find_event_relations(&self) {
812        let room_id = room_id!("!r0:matrix.org");
813        let another_room_id = room_id!("!r1:matrix.org");
814
815        let f = EventFactory::new().room(room_id).sender(*ALICE);
816
817        // Create event and related events for the first room.
818        let eid1 = event_id!("$event1:matrix.org");
819        let e1 = f.text_msg("comter").event_id(eid1).into_event();
820
821        let edit_eid1 = event_id!("$edit_event1:matrix.org");
822        let edit_e1 = f
823            .text_msg("* comté")
824            .event_id(edit_eid1)
825            .edit(eid1, RoomMessageEventContentWithoutRelation::text_plain("comté"))
826            .into_event();
827
828        let reaction_eid1 = event_id!("$reaction_event1:matrix.org");
829        let reaction_e1 = f.reaction(eid1, "👍").event_id(reaction_eid1).into_event();
830
831        let eid2 = event_id!("$event2:matrix.org");
832        let e2 = f.text_msg("galette saucisse").event_id(eid2).into_event();
833
834        // Create events for the second room.
835        let f = f.room(another_room_id);
836
837        let eid3 = event_id!("$event3:matrix.org");
838        let e3 = f.text_msg("gruyère").event_id(eid3).into_event();
839
840        let reaction_eid3 = event_id!("$reaction_event3:matrix.org");
841        let reaction_e3 = f.reaction(eid3, "👍").event_id(reaction_eid3).into_event();
842
843        // Save All The Things!
844        self.save_event(room_id, e1).await.unwrap();
845        self.save_event(room_id, edit_e1).await.unwrap();
846        self.save_event(room_id, reaction_e1.clone()).await.unwrap();
847        self.save_event(room_id, e2).await.unwrap();
848        self.save_event(another_room_id, e3).await.unwrap();
849        self.save_event(another_room_id, reaction_e3).await.unwrap();
850
851        // Finding relations without a filter returns all of them.
852        let relations = self.find_event_relations(room_id, eid1, None).await.unwrap();
853        assert_eq!(relations.len(), 2);
854        // The position is `None` for items outside the linked chunk.
855        assert!(
856            relations
857                .iter()
858                .any(|(ev, pos)| ev.event_id().as_deref() == Some(edit_eid1) && pos.is_none())
859        );
860        assert!(
861            relations
862                .iter()
863                .any(|(ev, pos)| ev.event_id().as_deref() == Some(reaction_eid1) && pos.is_none())
864        );
865
866        // Finding relations with a filter only returns a subset.
867        let relations = self
868            .find_event_relations(room_id, eid1, Some(&[RelationType::Replacement]))
869            .await
870            .unwrap();
871        assert_eq!(relations.len(), 1);
872        assert_eq!(relations[0].0.event_id().as_deref(), Some(edit_eid1));
873
874        let relations = self
875            .find_event_relations(
876                room_id,
877                eid1,
878                Some(&[RelationType::Replacement, RelationType::Annotation]),
879            )
880            .await
881            .unwrap();
882        assert_eq!(relations.len(), 2);
883        assert!(relations.iter().any(|r| r.0.event_id().as_deref() == Some(edit_eid1)));
884        assert!(relations.iter().any(|r| r.0.event_id().as_deref() == Some(reaction_eid1)));
885
886        // We can't find relations using the wrong room.
887        let relations = self
888            .find_event_relations(another_room_id, eid1, Some(&[RelationType::Replacement]))
889            .await
890            .unwrap();
891        assert!(relations.is_empty());
892
893        // But if an event exists in the linked chunk, we may have its position when
894        // it's found as a relationship.
895
896        // Add reaction_e1 to the room's linked chunk.
897        self.handle_linked_chunk_updates(
898            LinkedChunkId::Room(room_id),
899            vec![
900                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
901                Update::PushItems { at: Position::new(CId::new(0), 0), items: vec![reaction_e1] },
902            ],
903        )
904        .await
905        .unwrap();
906
907        // When looking for aggregations to e1, we should have the position for
908        // reaction_e1.
909        let relations = self.find_event_relations(room_id, eid1, None).await.unwrap();
910
911        // The position is set for `reaction_eid1` now.
912        assert!(relations.iter().any(|(ev, pos)| {
913            ev.event_id().as_deref() == Some(reaction_eid1)
914                && *pos == Some(Position::new(CId::new(0), 0))
915        }));
916
917        // But it's still not set for the other related events.
918        assert!(
919            relations
920                .iter()
921                .any(|(ev, pos)| ev.event_id().as_deref() == Some(edit_eid1) && pos.is_none())
922        );
923    }
924
925    async fn test_get_room_events(&self) {
926        let room_id = room_id!("!r0:matrix.org");
927        let another_room_id = room_id!("!r1:matrix.org");
928        let linked_chunk_id = LinkedChunkId::Room(room_id);
929        let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
930        let event = |msg: &str| make_test_event(room_id, msg);
931
932        let event_comte = event("comté");
933        let event_gruyere = event("gruyère");
934        let event_stilton = event("stilton");
935
936        // Add one event in one room.
937        self.handle_linked_chunk_updates(
938            linked_chunk_id,
939            vec![
940                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
941                Update::PushItems {
942                    at: Position::new(CId::new(0), 0),
943                    items: vec![event_comte.clone(), event_gruyere.clone()],
944                },
945            ],
946        )
947        .await
948        .unwrap();
949
950        // Add an event in a different room.
951        self.handle_linked_chunk_updates(
952            another_linked_chunk_id,
953            vec![
954                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
955                Update::PushItems {
956                    at: Position::new(CId::new(0), 0),
957                    items: vec![event_stilton.clone()],
958                },
959            ],
960        )
961        .await
962        .unwrap();
963
964        // Now let's find the events.
965        let events = self.get_room_events(room_id).await.expect("failed to query for room events");
966
967        assert_eq!(events.len(), 2);
968
969        let got_ids: Vec<_> = events.into_iter().map(|ev| ev.event_id()).collect();
970        let expected_ids = vec![event_comte.event_id(), event_gruyere.event_id()];
971
972        for expected in expected_ids {
973            assert!(
974                got_ids.contains(&expected),
975                "Expected event {expected:?} not in got events: {got_ids:?}."
976            );
977        }
978    }
979
980    async fn test_save_event(&self) {
981        let room_id = room_id!("!r0:matrix.org");
982        let another_room_id = room_id!("!r1:matrix.org");
983
984        let event = |msg: &str| make_test_event(room_id, msg);
985        let event_comte = event("comté");
986        let event_gruyere = event("gruyère");
987
988        // Add one event in one room.
989        self.save_event(room_id, event_comte.clone()).await.unwrap();
990
991        // Add another event in another room.
992        self.save_event(another_room_id, event_gruyere.clone()).await.unwrap();
993
994        // Events can be found, when searched in their own rooms.
995        let event = self
996            .find_event(room_id, event_comte.event_id().unwrap().as_ref())
997            .await
998            .expect("failed to query for finding an event")
999            .expect("failed to find an event");
1000        assert_eq!(event.event_id(), event_comte.event_id());
1001
1002        let event = self
1003            .find_event(another_room_id, event_gruyere.event_id().unwrap().as_ref())
1004            .await
1005            .expect("failed to query for finding an event")
1006            .expect("failed to find an event");
1007        assert_eq!(event.event_id(), event_gruyere.event_id());
1008
1009        // But they won't be returned when searching in the wrong room.
1010        assert!(
1011            self.find_event(another_room_id, event_comte.event_id().unwrap().as_ref())
1012                .await
1013                .expect("failed to query for finding an event")
1014                .is_none()
1015        );
1016        assert!(
1017            self.find_event(room_id, event_gruyere.event_id().unwrap().as_ref())
1018                .await
1019                .expect("failed to query for finding an event")
1020                .is_none()
1021        );
1022    }
1023
1024    async fn test_thread_vs_room_linked_chunk(&self) {
1025        let room_id = room_id!("!r0:matrix.org");
1026
1027        let event = |msg: &str| make_test_event(room_id, msg);
1028
1029        let thread1_ev = event("comté");
1030        let thread2_ev = event("gruyère");
1031        let thread2_ev2 = event("beaufort");
1032        let room_ev = event("brillat savarin triple crème");
1033
1034        let thread_root1 = event("thread1");
1035        let thread_root2 = event("thread2");
1036
1037        // Add one event in a thread linked chunk.
1038        self.handle_linked_chunk_updates(
1039            LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap().as_ref()),
1040            vec![
1041                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1042                Update::PushItems {
1043                    at: Position::new(CId::new(0), 0),
1044                    items: vec![thread1_ev.clone()],
1045                },
1046            ],
1047        )
1048        .await
1049        .unwrap();
1050
1051        // Add one event in another thread linked chunk (same room).
1052        self.handle_linked_chunk_updates(
1053            LinkedChunkId::Thread(room_id, thread_root2.event_id().unwrap().as_ref()),
1054            vec![
1055                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1056                Update::PushItems {
1057                    at: Position::new(CId::new(0), 0),
1058                    items: vec![thread2_ev.clone(), thread2_ev2.clone()],
1059                },
1060            ],
1061        )
1062        .await
1063        .unwrap();
1064
1065        // Add another event to the room linked chunk.
1066        self.handle_linked_chunk_updates(
1067            LinkedChunkId::Room(room_id),
1068            vec![
1069                Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1070                Update::PushItems {
1071                    at: Position::new(CId::new(0), 0),
1072                    items: vec![room_ev.clone()],
1073                },
1074            ],
1075        )
1076        .await
1077        .unwrap();
1078
1079        // All the events can be found with `find_event()` for the room.
1080        self.find_event(room_id, thread2_ev.event_id().unwrap().as_ref())
1081            .await
1082            .expect("failed to query for finding an event")
1083            .expect("failed to find thread1_ev");
1084
1085        self.find_event(room_id, thread2_ev.event_id().unwrap().as_ref())
1086            .await
1087            .expect("failed to query for finding an event")
1088            .expect("failed to find thread2_ev");
1089
1090        self.find_event(room_id, thread2_ev2.event_id().unwrap().as_ref())
1091            .await
1092            .expect("failed to query for finding an event")
1093            .expect("failed to find thread2_ev2");
1094
1095        self.find_event(room_id, room_ev.event_id().unwrap().as_ref())
1096            .await
1097            .expect("failed to query for finding an event")
1098            .expect("failed to find room_ev");
1099
1100        // Finding duplicates operates based on the linked chunk id.
1101        let dups = self
1102            .filter_duplicated_events(
1103                LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap().as_ref()),
1104                vec![
1105                    thread1_ev.event_id().unwrap().to_owned(),
1106                    room_ev.event_id().unwrap().to_owned(),
1107                ],
1108            )
1109            .await
1110            .unwrap();
1111        assert_eq!(dups.len(), 1);
1112        assert_eq!(dups[0].0, thread1_ev.event_id().unwrap());
1113
1114        // Loading all chunks operates based on the linked chunk id.
1115        let all_chunks = self
1116            .load_all_chunks(LinkedChunkId::Thread(
1117                room_id,
1118                thread_root2.event_id().unwrap().as_ref(),
1119            ))
1120            .await
1121            .unwrap();
1122        assert_eq!(all_chunks.len(), 1);
1123        assert_eq!(all_chunks[0].identifier, CId::new(0));
1124        assert_let!(ChunkContent::Items(observed_items) = all_chunks[0].content.clone());
1125        assert_eq!(observed_items.len(), 2);
1126        assert_eq!(observed_items[0].event_id(), thread2_ev.event_id());
1127        assert_eq!(observed_items[1].event_id(), thread2_ev2.event_id());
1128
1129        // Loading the metadata of all chunks operates based on the linked chunk
1130        // id.
1131        let metas = self
1132            .load_all_chunks_metadata(LinkedChunkId::Thread(
1133                room_id,
1134                thread_root2.event_id().unwrap().as_ref(),
1135            ))
1136            .await
1137            .unwrap();
1138        assert_eq!(metas.len(), 1);
1139        assert_eq!(metas[0].identifier, CId::new(0));
1140        assert_eq!(metas[0].num_items, 2);
1141
1142        // Loading the last chunk operates based on the linked chunk id.
1143        let (last_chunk, _chunk_identifier_generator) = self
1144            .load_last_chunk(LinkedChunkId::Thread(
1145                room_id,
1146                thread_root1.event_id().unwrap().as_ref(),
1147            ))
1148            .await
1149            .unwrap();
1150        let last_chunk = last_chunk.unwrap();
1151        assert_eq!(last_chunk.identifier, CId::new(0));
1152        assert_let!(ChunkContent::Items(observed_items) = last_chunk.content);
1153        assert_eq!(observed_items.len(), 1);
1154        assert_eq!(observed_items[0].event_id(), thread1_ev.event_id());
1155    }
1156}
1157
1158/// Macro building to allow your `EventCacheStore` implementation to run the
1159/// entire tests suite locally.
1160///
1161/// You need to provide a `async fn get_event_cache_store() ->
1162/// EventCacheStoreResult<impl EventCacheStore>` providing a fresh event cache
1163/// store on the same level you invoke the macro.
1164///
1165/// ## Usage Example:
1166/// ```no_run
1167/// # use matrix_sdk_base::event_cache::store::{
1168/// #    EventCacheStore,
1169/// #    MemoryStore as MyStore,
1170/// #    Result as EventCacheStoreResult,
1171/// # };
1172///
1173/// #[cfg(test)]
1174/// mod tests {
1175///     use super::{EventCacheStore, EventCacheStoreResult, MyStore};
1176///
1177///     async fn get_event_cache_store()
1178///     -> EventCacheStoreResult<impl EventCacheStore> {
1179///         Ok(MyStore::new())
1180///     }
1181///
1182///     event_cache_store_integration_tests!();
1183/// }
1184/// ```
1185#[allow(unused_macros, unused_extern_crates)]
1186#[macro_export]
1187macro_rules! event_cache_store_integration_tests {
1188    () => {
1189        mod event_cache_store_integration_tests {
1190            use matrix_sdk_test::async_test;
1191            use $crate::event_cache::store::{
1192                EventCacheStoreIntegrationTests, IntoEventCacheStore,
1193            };
1194
1195            use super::get_event_cache_store;
1196
1197            #[async_test]
1198            async fn test_handle_updates_and_rebuild_linked_chunk() {
1199                let event_cache_store =
1200                    get_event_cache_store().await.unwrap().into_event_cache_store();
1201                event_cache_store.test_handle_updates_and_rebuild_linked_chunk().await;
1202            }
1203
1204            #[async_test]
1205            async fn test_linked_chunk_incremental_loading() {
1206                let event_cache_store =
1207                    get_event_cache_store().await.unwrap().into_event_cache_store();
1208                event_cache_store.test_linked_chunk_incremental_loading().await;
1209            }
1210
1211            #[async_test]
1212            async fn test_rebuild_empty_linked_chunk() {
1213                let event_cache_store =
1214                    get_event_cache_store().await.unwrap().into_event_cache_store();
1215                event_cache_store.test_rebuild_empty_linked_chunk().await;
1216            }
1217
1218            #[async_test]
1219            async fn test_load_all_chunks_metadata() {
1220                let event_cache_store =
1221                    get_event_cache_store().await.unwrap().into_event_cache_store();
1222                event_cache_store.test_load_all_chunks_metadata().await;
1223            }
1224
1225            #[async_test]
1226            async fn test_clear_all_linked_chunks() {
1227                let event_cache_store =
1228                    get_event_cache_store().await.unwrap().into_event_cache_store();
1229                event_cache_store.test_clear_all_linked_chunks().await;
1230            }
1231
1232            #[async_test]
1233            async fn test_remove_room() {
1234                let event_cache_store =
1235                    get_event_cache_store().await.unwrap().into_event_cache_store();
1236                event_cache_store.test_remove_room().await;
1237            }
1238
1239            #[async_test]
1240            async fn test_filter_duplicated_events() {
1241                let event_cache_store =
1242                    get_event_cache_store().await.unwrap().into_event_cache_store();
1243                event_cache_store.test_filter_duplicated_events().await;
1244            }
1245
1246            #[async_test]
1247            async fn test_find_event() {
1248                let event_cache_store =
1249                    get_event_cache_store().await.unwrap().into_event_cache_store();
1250                event_cache_store.test_find_event().await;
1251            }
1252
1253            #[async_test]
1254            async fn test_find_event_relations() {
1255                let event_cache_store =
1256                    get_event_cache_store().await.unwrap().into_event_cache_store();
1257                event_cache_store.test_find_event_relations().await;
1258            }
1259
1260            #[async_test]
1261            async fn test_get_room_events() {
1262                let event_cache_store =
1263                    get_event_cache_store().await.unwrap().into_event_cache_store();
1264                event_cache_store.test_get_room_events().await;
1265            }
1266
1267            #[async_test]
1268            async fn test_save_event() {
1269                let event_cache_store =
1270                    get_event_cache_store().await.unwrap().into_event_cache_store();
1271                event_cache_store.test_save_event().await;
1272            }
1273
1274            #[async_test]
1275            async fn test_thread_vs_room_linked_chunk() {
1276                let event_cache_store =
1277                    get_event_cache_store().await.unwrap().into_event_cache_store();
1278                event_cache_store.test_thread_vs_room_linked_chunk().await;
1279            }
1280        }
1281    };
1282}
1283
1284/// Macro generating tests for the event cache store, related to time (mostly
1285/// for the cross-process lock).
1286#[allow(unused_macros)]
1287#[macro_export]
1288macro_rules! event_cache_store_integration_tests_time {
1289    () => {
1290        mod event_cache_store_integration_tests_time {
1291            use std::time::Duration;
1292
1293            #[cfg(all(target_family = "wasm", target_os = "unknown"))]
1294            use gloo_timers::future::sleep;
1295            use matrix_sdk_test::async_test;
1296            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
1297            use tokio::time::sleep;
1298            use $crate::event_cache::store::IntoEventCacheStore;
1299
1300            use super::get_event_cache_store;
1301
1302            #[async_test]
1303            async fn test_lease_locks() {
1304                let store = get_event_cache_store().await.unwrap().into_event_cache_store();
1305
1306                let acquired0 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
1307                assert!(acquired0);
1308
1309                // Should extend the lease automatically (same holder).
1310                let acquired2 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
1311                assert!(acquired2);
1312
1313                // Should extend the lease automatically (same holder + time is ok).
1314                let acquired3 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
1315                assert!(acquired3);
1316
1317                // Another attempt at taking the lock should fail, because it's taken.
1318                let acquired4 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1319                assert!(!acquired4);
1320
1321                // Even if we insist.
1322                let acquired5 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1323                assert!(!acquired5);
1324
1325                // That's a nice test we got here, go take a little nap.
1326                sleep(Duration::from_millis(50)).await;
1327
1328                // Still too early.
1329                let acquired55 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1330                assert!(!acquired55);
1331
1332                // Ok you can take another nap then.
1333                sleep(Duration::from_millis(250)).await;
1334
1335                // At some point, we do get the lock.
1336                let acquired6 = store.try_take_leased_lock(0, "key", "bob").await.unwrap();
1337                assert!(acquired6);
1338
1339                sleep(Duration::from_millis(1)).await;
1340
1341                // The other gets it almost immediately too.
1342                let acquired7 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
1343                assert!(acquired7);
1344
1345                sleep(Duration::from_millis(1)).await;
1346
1347                // But when we take a longer lease...
1348                let acquired8 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1349                assert!(acquired8);
1350
1351                // It blocks the other user.
1352                let acquired9 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
1353                assert!(!acquired9);
1354
1355                // We can hold onto our lease.
1356                let acquired10 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1357                assert!(acquired10);
1358            }
1359        }
1360    };
1361}