Skip to main content

matrix_sdk_base/event_cache/store/
integration_tests.rs

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