Skip to main content

matrix_sdk_base/media/store/
integration_tests.rs

1// Copyright 2025 Kévin Commaille
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 `MediaStoreInner`
16//! implementations.
17
18use ruma::{
19    events::room::MediaSource,
20    media::Method,
21    mxc_uri, owned_mxc_uri,
22    time::{Duration, SystemTime},
23    uint,
24};
25
26use super::{MediaRetentionPolicy, MediaStoreInner, media_service::IgnoreMediaRetentionPolicy};
27use crate::media::{
28    MediaFormat, MediaRequestParameters, MediaThumbnailSettings, store::MediaStore,
29};
30
31/// [`MediaStoreInner`] integration tests.
32///
33/// This trait is not meant to be used directly, but will be used with the
34/// `media_store_inner_integration_tests!` macro.
35#[allow(async_fn_in_trait)]
36pub trait MediaStoreInnerIntegrationTests {
37    /// Test media retention policy storage.
38    async fn test_store_media_retention_policy(&self);
39
40    /// Test media content's retention policy max file size.
41    async fn test_media_max_file_size(&self);
42
43    /// Test media content's retention policy max cache size.
44    async fn test_media_max_cache_size(&self);
45
46    /// Test media content's retention policy expiry.
47    async fn test_media_expiry(&self);
48
49    /// Test [`IgnoreMediaRetentionPolicy`] with the media content's retention
50    /// policy max sizes.
51    async fn test_media_ignore_max_size(&self);
52
53    /// Test [`IgnoreMediaRetentionPolicy`] with the media content's retention
54    /// policy expiry.
55    async fn test_media_ignore_expiry(&self);
56
57    /// Test last media cleanup time storage.
58    async fn test_store_last_media_cleanup_time(&self);
59}
60
61impl<Store> MediaStoreInnerIntegrationTests for Store
62where
63    Store: MediaStoreInner + std::fmt::Debug,
64{
65    async fn test_store_media_retention_policy(&self) {
66        let stored = self.media_retention_policy_inner().await.unwrap();
67        assert!(stored.is_none());
68
69        let policy = MediaRetentionPolicy::default();
70        self.set_media_retention_policy_inner(policy).await.unwrap();
71
72        let stored = self.media_retention_policy_inner().await.unwrap();
73        assert_eq!(stored, Some(policy));
74    }
75
76    async fn test_media_max_file_size(&self) {
77        let time = SystemTime::now();
78
79        // 256 bytes content.
80        let content_big = vec![0; 256];
81        let uri_big = owned_mxc_uri!("mxc://localhost/big-media");
82        let request_big = MediaRequestParameters {
83            source: MediaSource::Plain(uri_big),
84            format: MediaFormat::File,
85        };
86
87        // 128 bytes content.
88        let content_avg = vec![0; 128];
89        let uri_avg = owned_mxc_uri!("mxc://localhost/average-media");
90        let request_avg = MediaRequestParameters {
91            source: MediaSource::Plain(uri_avg),
92            format: MediaFormat::File,
93        };
94
95        // 64 bytes content.
96        let content_small = vec![0; 64];
97        let uri_small = owned_mxc_uri!("mxc://localhost/small-media");
98        let request_small = MediaRequestParameters {
99            source: MediaSource::Plain(uri_small),
100            format: MediaFormat::File,
101        };
102
103        // First, with a policy that doesn't accept the big media.
104        let policy = MediaRetentionPolicy::empty().with_max_file_size(Some(200));
105
106        self.add_media_content_inner(
107            &request_big,
108            content_big.clone(),
109            time,
110            policy,
111            IgnoreMediaRetentionPolicy::No,
112        )
113        .await
114        .unwrap();
115        self.add_media_content_inner(
116            &request_avg,
117            content_avg.clone(),
118            time,
119            policy,
120            IgnoreMediaRetentionPolicy::No,
121        )
122        .await
123        .unwrap();
124        self.add_media_content_inner(
125            &request_small,
126            content_small,
127            time,
128            policy,
129            IgnoreMediaRetentionPolicy::No,
130        )
131        .await
132        .unwrap();
133
134        // The big content was NOT cached but the others were.
135        let stored = self.get_media_content_inner(&request_big, time).await.unwrap();
136        assert!(stored.is_none());
137        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
138        assert!(stored.is_some());
139        let stored = self.get_media_content_inner(&request_small, time).await.unwrap();
140        assert!(stored.is_some());
141
142        // A cleanup doesn't have any effect.
143        self.clean_inner(policy, time).await.unwrap();
144
145        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
146        assert!(stored.is_some());
147        let stored = self.get_media_content_inner(&request_small, time).await.unwrap();
148        assert!(stored.is_some());
149
150        // Change to a policy that doesn't accept the average media.
151        let policy = MediaRetentionPolicy::empty().with_max_file_size(Some(100));
152
153        // The cleanup removes the average media.
154        self.clean_inner(policy, time).await.unwrap();
155
156        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
157        assert!(stored.is_none());
158        let stored = self.get_media_content_inner(&request_small, time).await.unwrap();
159        assert!(stored.is_some());
160
161        // Caching big and average media doesn't work.
162        self.add_media_content_inner(
163            &request_big,
164            content_big.clone(),
165            time,
166            policy,
167            IgnoreMediaRetentionPolicy::No,
168        )
169        .await
170        .unwrap();
171        self.add_media_content_inner(
172            &request_avg,
173            content_avg.clone(),
174            time,
175            policy,
176            IgnoreMediaRetentionPolicy::No,
177        )
178        .await
179        .unwrap();
180
181        let stored = self.get_media_content_inner(&request_big, time).await.unwrap();
182        assert!(stored.is_none());
183        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
184        assert!(stored.is_none());
185
186        // If there are both a cache size and a file size, the minimum value is
187        // used.
188        let policy = MediaRetentionPolicy::empty()
189            .with_max_cache_size(Some(200))
190            .with_max_file_size(Some(1000));
191
192        // Caching big doesn't work.
193        self.add_media_content_inner(
194            &request_big,
195            content_big.clone(),
196            time,
197            policy,
198            IgnoreMediaRetentionPolicy::No,
199        )
200        .await
201        .unwrap();
202        self.add_media_content_inner(
203            &request_avg,
204            content_avg.clone(),
205            time,
206            policy,
207            IgnoreMediaRetentionPolicy::No,
208        )
209        .await
210        .unwrap();
211
212        let stored = self.get_media_content_inner(&request_big, time).await.unwrap();
213        assert!(stored.is_none());
214        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
215        assert!(stored.is_some());
216
217        // Change to a policy that doesn't accept the average media.
218        let policy = MediaRetentionPolicy::empty()
219            .with_max_cache_size(Some(100))
220            .with_max_file_size(Some(1000));
221
222        // The cleanup removes the average media.
223        self.clean_inner(policy, time).await.unwrap();
224
225        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
226        assert!(stored.is_none());
227        let stored = self.get_media_content_inner(&request_small, time).await.unwrap();
228        assert!(stored.is_some());
229
230        // Caching big and average media doesn't work.
231        self.add_media_content_inner(
232            &request_big,
233            content_big,
234            time,
235            policy,
236            IgnoreMediaRetentionPolicy::No,
237        )
238        .await
239        .unwrap();
240        self.add_media_content_inner(
241            &request_avg,
242            content_avg,
243            time,
244            policy,
245            IgnoreMediaRetentionPolicy::No,
246        )
247        .await
248        .unwrap();
249
250        let stored = self.get_media_content_inner(&request_big, time).await.unwrap();
251        assert!(stored.is_none());
252        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
253        assert!(stored.is_none());
254    }
255
256    async fn test_media_max_cache_size(&self) {
257        // 256 bytes content.
258        let content_big = vec![0; 256];
259        let uri_big = owned_mxc_uri!("mxc://localhost/big-media");
260        let request_big = MediaRequestParameters {
261            source: MediaSource::Plain(uri_big),
262            format: MediaFormat::File,
263        };
264
265        // 128 bytes content.
266        let content_avg = vec![0; 128];
267        let uri_avg = mxc_uri!("mxc://localhost/average-media");
268        let request_avg = MediaRequestParameters {
269            source: MediaSource::Plain(uri_avg.to_owned()),
270            format: MediaFormat::File,
271        };
272
273        // 64 bytes content.
274        let content_small = vec![0; 64];
275        let uri_small_1 = owned_mxc_uri!("mxc://localhost/small-media-1");
276        let request_small_1 = MediaRequestParameters {
277            source: MediaSource::Plain(uri_small_1),
278            format: MediaFormat::File,
279        };
280        let uri_small_2 = owned_mxc_uri!("mxc://localhost/small-media-2");
281        let request_small_2 = MediaRequestParameters {
282            source: MediaSource::Plain(uri_small_2),
283            format: MediaFormat::File,
284        };
285        let uri_small_3 = owned_mxc_uri!("mxc://localhost/small-media-3");
286        let request_small_3 = MediaRequestParameters {
287            source: MediaSource::Plain(uri_small_3),
288            format: MediaFormat::File,
289        };
290        let uri_small_4 = owned_mxc_uri!("mxc://localhost/small-media-4");
291        let request_small_4 = MediaRequestParameters {
292            source: MediaSource::Plain(uri_small_4),
293            format: MediaFormat::File,
294        };
295        let uri_small_5 = owned_mxc_uri!("mxc://localhost/small-media-5");
296        let request_small_5 = MediaRequestParameters {
297            source: MediaSource::Plain(uri_small_5),
298            format: MediaFormat::File,
299        };
300
301        // A policy that doesn't accept the big media.
302        let policy = MediaRetentionPolicy::empty().with_max_cache_size(Some(200));
303
304        // Try to add all the content at different times.
305        let mut time = SystemTime::UNIX_EPOCH;
306        self.add_media_content_inner(
307            &request_big,
308            content_big,
309            time,
310            policy,
311            IgnoreMediaRetentionPolicy::No,
312        )
313        .await
314        .unwrap();
315        time += Duration::from_secs(1);
316        self.add_media_content_inner(
317            &request_small_1,
318            content_small.clone(),
319            time,
320            policy,
321            IgnoreMediaRetentionPolicy::No,
322        )
323        .await
324        .unwrap();
325        time += Duration::from_secs(1);
326        self.add_media_content_inner(
327            &request_small_2,
328            content_small.clone(),
329            time,
330            policy,
331            IgnoreMediaRetentionPolicy::No,
332        )
333        .await
334        .unwrap();
335        time += Duration::from_secs(1);
336        self.add_media_content_inner(
337            &request_small_3,
338            content_small.clone(),
339            time,
340            policy,
341            IgnoreMediaRetentionPolicy::No,
342        )
343        .await
344        .unwrap();
345        time += Duration::from_secs(1);
346        self.add_media_content_inner(
347            &request_small_4,
348            content_small.clone(),
349            time,
350            policy,
351            IgnoreMediaRetentionPolicy::No,
352        )
353        .await
354        .unwrap();
355        time += Duration::from_secs(1);
356        self.add_media_content_inner(
357            &request_small_5,
358            content_small.clone(),
359            time,
360            policy,
361            IgnoreMediaRetentionPolicy::No,
362        )
363        .await
364        .unwrap();
365        time += Duration::from_secs(1);
366        self.add_media_content_inner(
367            &request_avg,
368            content_avg,
369            time,
370            policy,
371            IgnoreMediaRetentionPolicy::No,
372        )
373        .await
374        .unwrap();
375
376        // The big content was NOT cached but the others were.
377        time += Duration::from_secs(1);
378        let stored = self.get_media_content_inner(&request_big, time).await.unwrap();
379        assert!(stored.is_none());
380        time += Duration::from_secs(1);
381        let stored = self.get_media_content_inner(&request_small_1, time).await.unwrap();
382        assert!(stored.is_some());
383        time += Duration::from_secs(1);
384        let stored = self.get_media_content_inner(&request_small_2, time).await.unwrap();
385        assert!(stored.is_some());
386        time += Duration::from_secs(1);
387        let stored = self.get_media_content_inner(&request_small_3, time).await.unwrap();
388        assert!(stored.is_some());
389        time += Duration::from_secs(1);
390        let stored = self.get_media_content_inner(&request_small_4, time).await.unwrap();
391        assert!(stored.is_some());
392        time += Duration::from_secs(1);
393        let stored = self.get_media_content_inner(&request_small_5, time).await.unwrap();
394        assert!(stored.is_some());
395        time += Duration::from_secs(1);
396        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
397        assert!(stored.is_some());
398
399        // Cleanup removes the oldest content first.
400        time += Duration::from_secs(1);
401        self.clean_inner(policy, time).await.unwrap();
402
403        time += Duration::from_secs(1);
404        let stored = self.get_media_content_inner(&request_small_1, time).await.unwrap();
405        assert!(stored.is_none());
406        time += Duration::from_secs(1);
407        let stored = self.get_media_content_inner(&request_small_2, time).await.unwrap();
408        assert!(stored.is_none());
409        time += Duration::from_secs(1);
410        let stored = self.get_media_content_inner(&request_small_3, time).await.unwrap();
411        assert!(stored.is_none());
412        time += Duration::from_secs(1);
413        let stored = self.get_media_content_inner(&request_small_4, time).await.unwrap();
414        assert!(stored.is_none());
415        time += Duration::from_secs(1);
416        let stored = self.get_media_content_inner(&request_small_5, time).await.unwrap();
417        assert!(stored.is_some());
418        time += Duration::from_secs(1);
419        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
420        assert!(stored.is_some());
421
422        // Reinsert the small medias that were removed.
423        time += Duration::from_secs(1);
424        self.add_media_content_inner(
425            &request_small_1,
426            content_small.clone(),
427            time,
428            policy,
429            IgnoreMediaRetentionPolicy::No,
430        )
431        .await
432        .unwrap();
433        time += Duration::from_secs(1);
434        self.add_media_content_inner(
435            &request_small_2,
436            content_small.clone(),
437            time,
438            policy,
439            IgnoreMediaRetentionPolicy::No,
440        )
441        .await
442        .unwrap();
443        time += Duration::from_secs(1);
444        self.add_media_content_inner(
445            &request_small_3,
446            content_small.clone(),
447            time,
448            policy,
449            IgnoreMediaRetentionPolicy::No,
450        )
451        .await
452        .unwrap();
453        time += Duration::from_secs(1);
454        self.add_media_content_inner(
455            &request_small_4,
456            content_small,
457            time,
458            policy,
459            IgnoreMediaRetentionPolicy::No,
460        )
461        .await
462        .unwrap();
463
464        // Check that they are cached.
465        time += Duration::from_secs(1);
466        let stored = self.get_media_content_inner(&request_small_1, time).await.unwrap();
467        assert!(stored.is_some());
468        time += Duration::from_secs(1);
469        let stored = self.get_media_content_inner(&request_small_2, time).await.unwrap();
470        assert!(stored.is_some());
471        time += Duration::from_secs(1);
472        let stored = self.get_media_content_inner(&request_small_3, time).await.unwrap();
473        assert!(stored.is_some());
474        time += Duration::from_secs(1);
475        let stored = self.get_media_content_inner(&request_small_4, time).await.unwrap();
476        assert!(stored.is_some());
477
478        // Access small_5 too so its last access is updated too.
479        time += Duration::from_secs(1);
480        let stored = self.get_media_content_inner(&request_small_5, time).await.unwrap();
481        assert!(stored.is_some());
482
483        // Cleanup still removes the oldest content first, which is not the same
484        // as before.
485        time += Duration::from_secs(1);
486        tracing::info!(?self, "before");
487        self.clean_inner(policy, time).await.unwrap();
488        tracing::info!(?self, "after");
489        time += Duration::from_secs(1);
490        let stored = self.get_media_content_inner(&request_small_1, time).await.unwrap();
491        assert!(stored.is_none());
492        time += Duration::from_secs(1);
493        let stored = self.get_media_content_inner(&request_small_2, time).await.unwrap();
494        assert!(stored.is_none());
495        time += Duration::from_secs(1);
496        let stored = self.get_media_content_inner(&request_small_3, time).await.unwrap();
497        assert!(stored.is_some());
498        time += Duration::from_secs(1);
499        let stored = self.get_media_content_inner(&request_small_4, time).await.unwrap();
500        assert!(stored.is_some());
501        time += Duration::from_secs(1);
502        let stored = self.get_media_content_inner(&request_small_5, time).await.unwrap();
503        assert!(stored.is_some());
504        time += Duration::from_secs(1);
505        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
506        assert!(stored.is_none());
507    }
508
509    async fn test_media_expiry(&self) {
510        // 64 bytes content.
511        let content = vec![0; 64];
512
513        let uri_1 = owned_mxc_uri!("mxc://localhost/media-1");
514        let request_1 =
515            MediaRequestParameters { source: MediaSource::Plain(uri_1), format: MediaFormat::File };
516        let uri_2 = owned_mxc_uri!("mxc://localhost/media-2");
517        let request_2 =
518            MediaRequestParameters { source: MediaSource::Plain(uri_2), format: MediaFormat::File };
519        let uri_3 = owned_mxc_uri!("mxc://localhost/media-3");
520        let request_3 =
521            MediaRequestParameters { source: MediaSource::Plain(uri_3), format: MediaFormat::File };
522        let uri_4 = owned_mxc_uri!("mxc://localhost/media-4");
523        let request_4 =
524            MediaRequestParameters { source: MediaSource::Plain(uri_4), format: MediaFormat::File };
525        let uri_5 = owned_mxc_uri!("mxc://localhost/media-5");
526        let request_5 =
527            MediaRequestParameters { source: MediaSource::Plain(uri_5), format: MediaFormat::File };
528
529        // A policy with 30 seconds expiry.
530        let policy =
531            MediaRetentionPolicy::empty().with_last_access_expiry(Some(Duration::from_secs(30)));
532
533        // Add all the content at different times.
534        let mut time = SystemTime::UNIX_EPOCH;
535        self.add_media_content_inner(
536            &request_1,
537            content.clone(),
538            time,
539            policy,
540            IgnoreMediaRetentionPolicy::No,
541        )
542        .await
543        .unwrap();
544        time += Duration::from_secs(1);
545        self.add_media_content_inner(
546            &request_2,
547            content.clone(),
548            time,
549            policy,
550            IgnoreMediaRetentionPolicy::No,
551        )
552        .await
553        .unwrap();
554        time += Duration::from_secs(1);
555        self.add_media_content_inner(
556            &request_3,
557            content.clone(),
558            time,
559            policy,
560            IgnoreMediaRetentionPolicy::No,
561        )
562        .await
563        .unwrap();
564        time += Duration::from_secs(1);
565        self.add_media_content_inner(
566            &request_4,
567            content.clone(),
568            time,
569            policy,
570            IgnoreMediaRetentionPolicy::No,
571        )
572        .await
573        .unwrap();
574        time += Duration::from_secs(1);
575        self.add_media_content_inner(
576            &request_5,
577            content,
578            time,
579            policy,
580            IgnoreMediaRetentionPolicy::No,
581        )
582        .await
583        .unwrap();
584
585        // The content was cached.
586        time += Duration::from_secs(1);
587        let stored = self.get_media_content_inner(&request_1, time).await.unwrap();
588        assert!(stored.is_some());
589        time += Duration::from_secs(1);
590        let stored = self.get_media_content_inner(&request_2, time).await.unwrap();
591        assert!(stored.is_some());
592        time += Duration::from_secs(1);
593        let stored = self.get_media_content_inner(&request_3, time).await.unwrap();
594        assert!(stored.is_some());
595        time += Duration::from_secs(1);
596        let stored = self.get_media_content_inner(&request_4, time).await.unwrap();
597        assert!(stored.is_some());
598        time += Duration::from_secs(1);
599        let stored = self.get_media_content_inner(&request_5, time).await.unwrap();
600        assert!(stored.is_some());
601
602        // We are now at UNIX_EPOCH + 10 seconds, the oldest content was
603        // accessed 5 seconds ago.
604        time += Duration::from_secs(1);
605        assert_eq!(time, SystemTime::UNIX_EPOCH + Duration::from_secs(10));
606
607        // Cleanup has no effect, nothing has expired.
608        self.clean_inner(policy, time).await.unwrap();
609
610        time += Duration::from_secs(1);
611        let stored = self.get_media_content_inner(&request_1, time).await.unwrap();
612        assert!(stored.is_some());
613        time += Duration::from_secs(1);
614        let stored = self.get_media_content_inner(&request_2, time).await.unwrap();
615        assert!(stored.is_some());
616        time += Duration::from_secs(1);
617        let stored = self.get_media_content_inner(&request_3, time).await.unwrap();
618        assert!(stored.is_some());
619        time += Duration::from_secs(1);
620        let stored = self.get_media_content_inner(&request_4, time).await.unwrap();
621        assert!(stored.is_some());
622        time += Duration::from_secs(1);
623        let stored = self.get_media_content_inner(&request_5, time).await.unwrap();
624        assert!(stored.is_some());
625
626        // We are now at UNIX_EPOCH + 16 seconds, the oldest content was
627        // accessed 5 seconds ago.
628        time += Duration::from_secs(1);
629        assert_eq!(time, SystemTime::UNIX_EPOCH + Duration::from_secs(16));
630
631        // Jump 26 seconds in the future, so the 2 first media contents are
632        // expired.
633        time += Duration::from_secs(26);
634
635        // Cleanup removes the two oldest media contents.
636        self.clean_inner(policy, time).await.unwrap();
637
638        time += Duration::from_secs(1);
639        let stored = self.get_media_content_inner(&request_1, time).await.unwrap();
640        assert!(stored.is_none());
641        time += Duration::from_secs(1);
642        let stored = self.get_media_content_inner(&request_2, time).await.unwrap();
643        assert!(stored.is_none());
644        time += Duration::from_secs(1);
645        let stored = self.get_media_content_inner(&request_3, time).await.unwrap();
646        assert!(stored.is_some());
647        time += Duration::from_secs(1);
648        let stored = self.get_media_content_inner(&request_4, time).await.unwrap();
649        assert!(stored.is_some());
650        time += Duration::from_secs(1);
651        let stored = self.get_media_content_inner(&request_5, time).await.unwrap();
652        assert!(stored.is_some());
653    }
654
655    async fn test_media_ignore_max_size(&self) {
656        // 256 bytes content.
657        let content_big = vec![0; 256];
658        let uri_big = owned_mxc_uri!("mxc://localhost/big-media");
659        let request_big = MediaRequestParameters {
660            source: MediaSource::Plain(uri_big),
661            format: MediaFormat::File,
662        };
663
664        // 128 bytes content.
665        let content_avg = vec![0; 128];
666        let uri_avg = mxc_uri!("mxc://localhost/average-media");
667        let request_avg = MediaRequestParameters {
668            source: MediaSource::Plain(uri_avg.to_owned()),
669            format: MediaFormat::File,
670        };
671
672        // 64 bytes content.
673        let content_small = vec![0; 64];
674        let uri_small = owned_mxc_uri!("mxc://localhost/small-media-1");
675        let request_small = MediaRequestParameters {
676            source: MediaSource::Plain(uri_small),
677            format: MediaFormat::File,
678        };
679
680        // A policy that will result in only one media content in the cache,
681        // which is the average or small content, depending on the last access
682        // time.
683        let policy = MediaRetentionPolicy::empty().with_max_cache_size(Some(150));
684
685        // Try to add all the big content without ignoring the policy, it should
686        // fail.
687        let mut time = SystemTime::UNIX_EPOCH;
688        self.add_media_content_inner(
689            &request_big,
690            content_big.clone(),
691            time,
692            policy,
693            IgnoreMediaRetentionPolicy::No,
694        )
695        .await
696        .unwrap();
697
698        let stored = self.get_media_content_inner(&request_big, time).await.unwrap();
699        assert!(stored.is_none());
700
701        // Try to add it again but ignore the policy this time, it should
702        // succeed.
703        time += Duration::from_secs(1);
704        self.add_media_content_inner(
705            &request_big,
706            content_big,
707            time,
708            policy,
709            IgnoreMediaRetentionPolicy::Yes,
710        )
711        .await
712        .unwrap();
713
714        time += Duration::from_secs(1);
715        let stored = self.get_media_content_inner(&request_big, time).await.unwrap();
716        assert!(stored.is_some());
717
718        // Add the other contents.
719        time += Duration::from_secs(1);
720        self.add_media_content_inner(
721            &request_small,
722            content_small.clone(),
723            time,
724            policy,
725            IgnoreMediaRetentionPolicy::No,
726        )
727        .await
728        .unwrap();
729        time += Duration::from_secs(1);
730        self.add_media_content_inner(
731            &request_avg,
732            content_avg,
733            time,
734            policy,
735            IgnoreMediaRetentionPolicy::No,
736        )
737        .await
738        .unwrap();
739
740        // The other contents were added.
741        time += Duration::from_secs(1);
742        let stored = self.get_media_content_inner(&request_small, time).await.unwrap();
743        assert!(stored.is_some());
744        time += Duration::from_secs(1);
745        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
746        assert!(stored.is_some());
747
748        // Ignore the average content for now so the max cache size is not
749        // reached.
750        self.set_ignore_media_retention_policy_inner(&request_avg, IgnoreMediaRetentionPolicy::Yes)
751            .await
752            .unwrap();
753
754        // Because the big and average contents are ignored, cleanup has no
755        // effect.
756        time += Duration::from_secs(1);
757        self.clean_inner(policy, time).await.unwrap();
758
759        time += Duration::from_secs(1);
760        let stored = self.get_media_content_inner(&request_small, time).await.unwrap();
761        assert!(stored.is_some());
762        time += Duration::from_secs(1);
763        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
764        assert!(stored.is_some());
765        time += Duration::from_secs(1);
766        let stored = self.get_media_content_inner(&request_big, time).await.unwrap();
767        assert!(stored.is_some());
768
769        // Stop ignoring the big media, it should then be cleaned up.
770        self.set_ignore_media_retention_policy_inner(&request_big, IgnoreMediaRetentionPolicy::No)
771            .await
772            .unwrap();
773
774        time += Duration::from_secs(1);
775        self.clean_inner(policy, time).await.unwrap();
776
777        time += Duration::from_secs(1);
778        let stored = self.get_media_content_inner(&request_small, time).await.unwrap();
779        assert!(stored.is_some());
780        time += Duration::from_secs(1);
781        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
782        assert!(stored.is_some());
783        time += Duration::from_secs(1);
784        let stored = self.get_media_content_inner(&request_big, time).await.unwrap();
785        assert!(stored.is_none());
786
787        // Stop ignoring the average media. Since the cache size is bigger than
788        // the max, the content that was not the last accessed should be cleaned
789        // up.
790        self.set_ignore_media_retention_policy_inner(&request_avg, IgnoreMediaRetentionPolicy::No)
791            .await
792            .unwrap();
793
794        time += Duration::from_secs(1);
795        self.clean_inner(policy, time).await.unwrap();
796
797        time += Duration::from_secs(1);
798        let stored = self.get_media_content_inner(&request_small, time).await.unwrap();
799        assert!(stored.is_none());
800        time += Duration::from_secs(1);
801        let stored = self.get_media_content_inner(&request_avg, time).await.unwrap();
802        assert!(stored.is_some());
803        time += Duration::from_secs(1);
804        let stored = self.get_media_content_inner(&request_big, time).await.unwrap();
805        assert!(stored.is_none());
806    }
807
808    async fn test_media_ignore_expiry(&self) {
809        // 64 bytes content.
810        let content = vec![0; 64];
811
812        let uri_1 = owned_mxc_uri!("mxc://localhost/media-1");
813        let request_1 =
814            MediaRequestParameters { source: MediaSource::Plain(uri_1), format: MediaFormat::File };
815        let uri_2 = owned_mxc_uri!("mxc://localhost/media-2");
816        let request_2 =
817            MediaRequestParameters { source: MediaSource::Plain(uri_2), format: MediaFormat::File };
818        let uri_3 = owned_mxc_uri!("mxc://localhost/media-3");
819        let request_3 =
820            MediaRequestParameters { source: MediaSource::Plain(uri_3), format: MediaFormat::File };
821        let uri_4 = owned_mxc_uri!("mxc://localhost/media-4");
822        let request_4 =
823            MediaRequestParameters { source: MediaSource::Plain(uri_4), format: MediaFormat::File };
824        let uri_5 = owned_mxc_uri!("mxc://localhost/media-5");
825        let request_5 =
826            MediaRequestParameters { source: MediaSource::Plain(uri_5), format: MediaFormat::File };
827
828        // A policy with 30 seconds expiry.
829        let policy =
830            MediaRetentionPolicy::empty().with_last_access_expiry(Some(Duration::from_secs(30)));
831
832        // Add all the content at different times.
833        let mut time = SystemTime::UNIX_EPOCH;
834        self.add_media_content_inner(
835            &request_1,
836            content.clone(),
837            time,
838            policy,
839            IgnoreMediaRetentionPolicy::Yes,
840        )
841        .await
842        .unwrap();
843        time += Duration::from_secs(1);
844        self.add_media_content_inner(
845            &request_2,
846            content.clone(),
847            time,
848            policy,
849            IgnoreMediaRetentionPolicy::Yes,
850        )
851        .await
852        .unwrap();
853        time += Duration::from_secs(1);
854        self.add_media_content_inner(
855            &request_3,
856            content.clone(),
857            time,
858            policy,
859            IgnoreMediaRetentionPolicy::No,
860        )
861        .await
862        .unwrap();
863        time += Duration::from_secs(1);
864        self.add_media_content_inner(
865            &request_4,
866            content.clone(),
867            time,
868            policy,
869            IgnoreMediaRetentionPolicy::No,
870        )
871        .await
872        .unwrap();
873        time += Duration::from_secs(1);
874        self.add_media_content_inner(
875            &request_5,
876            content,
877            time,
878            policy,
879            IgnoreMediaRetentionPolicy::No,
880        )
881        .await
882        .unwrap();
883
884        // The content was cached.
885        time += Duration::from_secs(1);
886        let stored = self.get_media_content_inner(&request_1, time).await.unwrap();
887        assert!(stored.is_some());
888        time += Duration::from_secs(1);
889        let stored = self.get_media_content_inner(&request_2, time).await.unwrap();
890        assert!(stored.is_some());
891        time += Duration::from_secs(1);
892        let stored = self.get_media_content_inner(&request_3, time).await.unwrap();
893        assert!(stored.is_some());
894        time += Duration::from_secs(1);
895        let stored = self.get_media_content_inner(&request_4, time).await.unwrap();
896        assert!(stored.is_some());
897        time += Duration::from_secs(1);
898        let stored = self.get_media_content_inner(&request_5, time).await.unwrap();
899        assert!(stored.is_some());
900
901        // We advance of 120 seconds, all media should be expired.
902        time += Duration::from_secs(120);
903
904        // Cleanup removes all the media contents that are not ignored.
905        self.clean_inner(policy, time).await.unwrap();
906
907        time += Duration::from_secs(1);
908        let stored = self.get_media_content_inner(&request_1, time).await.unwrap();
909        assert!(stored.is_some());
910        time += Duration::from_secs(1);
911        let stored = self.get_media_content_inner(&request_2, time).await.unwrap();
912        assert!(stored.is_some());
913        time += Duration::from_secs(1);
914        let stored = self.get_media_content_inner(&request_3, time).await.unwrap();
915        assert!(stored.is_none());
916        time += Duration::from_secs(1);
917        let stored = self.get_media_content_inner(&request_4, time).await.unwrap();
918        assert!(stored.is_none());
919        time += Duration::from_secs(1);
920        let stored = self.get_media_content_inner(&request_5, time).await.unwrap();
921        assert!(stored.is_none());
922
923        // Do no ignore the content anymore.
924        self.set_ignore_media_retention_policy_inner(&request_1, IgnoreMediaRetentionPolicy::No)
925            .await
926            .unwrap();
927        self.set_ignore_media_retention_policy_inner(&request_2, IgnoreMediaRetentionPolicy::No)
928            .await
929            .unwrap();
930
931        // We advance of 120 seconds, all media should be expired again.
932        time += Duration::from_secs(120);
933
934        // Cleanup removes the remaining media contents.
935        self.clean_inner(policy, time).await.unwrap();
936
937        time += Duration::from_secs(1);
938        let stored = self.get_media_content_inner(&request_1, time).await.unwrap();
939        assert!(stored.is_none());
940        time += Duration::from_secs(1);
941        let stored = self.get_media_content_inner(&request_2, time).await.unwrap();
942        assert!(stored.is_none());
943        time += Duration::from_secs(1);
944        let stored = self.get_media_content_inner(&request_3, time).await.unwrap();
945        assert!(stored.is_none());
946        time += Duration::from_secs(1);
947        let stored = self.get_media_content_inner(&request_4, time).await.unwrap();
948        assert!(stored.is_none());
949        time += Duration::from_secs(1);
950        let stored = self.get_media_content_inner(&request_5, time).await.unwrap();
951        assert!(stored.is_none());
952    }
953
954    async fn test_store_last_media_cleanup_time(&self) {
955        let initial = self.last_media_cleanup_time_inner().await.unwrap();
956        let new_time = initial.unwrap_or_else(SystemTime::now) + Duration::from_secs(60);
957
958        // With an empty policy.
959        let policy = MediaRetentionPolicy::empty();
960        self.clean_inner(policy, new_time).await.unwrap();
961
962        let stored = self.last_media_cleanup_time_inner().await.unwrap();
963        assert_eq!(stored, initial);
964
965        // With the default policy.
966        let policy = MediaRetentionPolicy::default();
967        self.clean_inner(policy, new_time).await.unwrap();
968
969        let stored = self.last_media_cleanup_time_inner().await.unwrap();
970        assert_eq!(stored, Some(new_time));
971    }
972}
973
974/// Macro building to allow your [`MediaStoreInner`] implementation to run the
975/// entire tests suite locally.
976///
977/// Can be run with the `with_media_size_tests` argument to include more tests
978/// about the media cache retention policy based on content size. It is not
979/// recommended to run those in encrypted stores because the size of the
980/// encrypted content may vary compared to what the tests expect.
981///
982/// You need to provide an
983/// `async fn get_media_store() -> media::store::Result<Store>` that provides a
984/// fresh media store that implements `MediaStoreInner` on the same level you
985/// invoke the macro.
986///
987/// ## Usage example
988///
989/// ```no_run
990/// # use matrix_sdk_base::media::store::{
991/// #    MediaStore,
992/// #    MemoryMediaStore as MyStore,
993/// #    Result as MediaStoreResult,
994/// # };
995///
996/// #[cfg(test)]
997/// mod tests {
998///     use super::{MediaStoreResult, MyStore};
999///
1000///     async fn get_media_store() -> MediaStoreResult<MyStore> {
1001///         Ok(MyStore::new())
1002///     }
1003///
1004///     media_store_inner_integration_tests!();
1005/// }
1006/// ```
1007#[allow(unused_macros, unused_extern_crates)]
1008#[macro_export]
1009macro_rules! media_store_inner_integration_tests {
1010    (with_media_size_tests) => {
1011        mod media_store_inner_integration_tests {
1012            $crate::media_store_inner_integration_tests!(@inner);
1013
1014            #[async_test]
1015            async fn test_media_max_file_size() {
1016                let media_store_inner = get_media_store().await.unwrap();
1017                media_store_inner.test_media_max_file_size().await;
1018            }
1019
1020            #[async_test]
1021            async fn test_media_max_cache_size() {
1022                let media_store_inner = get_media_store().await.unwrap();
1023                media_store_inner.test_media_max_cache_size().await;
1024            }
1025
1026            #[async_test]
1027            async fn test_media_ignore_max_size() {
1028                let media_store_inner = get_media_store().await.unwrap();
1029                media_store_inner.test_media_ignore_max_size().await;
1030            }
1031        }
1032    };
1033
1034    () => {
1035        mod media_store_inner_integration_tests {
1036            $crate::media_store_inner_integration_tests!(@inner);
1037        }
1038    };
1039
1040    (@inner) => {
1041        use matrix_sdk_test::async_test;
1042        use $crate::media::store::MediaStoreInnerIntegrationTests;
1043
1044        use super::get_media_store;
1045
1046        #[async_test]
1047        async fn test_store_media_retention_policy() {
1048            let media_store_inner = get_media_store().await.unwrap();
1049            media_store_inner.test_store_media_retention_policy().await;
1050        }
1051
1052        #[async_test]
1053        async fn test_media_expiry() {
1054            let media_store_inner = get_media_store().await.unwrap();
1055            media_store_inner.test_media_expiry().await;
1056        }
1057
1058        #[async_test]
1059        async fn test_media_ignore_expiry() {
1060            let media_store_inner = get_media_store().await.unwrap();
1061            media_store_inner.test_media_ignore_expiry().await;
1062        }
1063
1064        #[async_test]
1065        async fn test_store_last_media_cleanup_time() {
1066            let media_store_inner = get_media_store().await.unwrap();
1067            media_store_inner.test_store_last_media_cleanup_time().await;
1068        }
1069    };
1070}
1071
1072/// [`MediaStore`] integration tests.
1073///
1074/// This trait is not meant to be used directly, but will be used with the
1075/// `media_store_inner_integration_tests!` macro.
1076#[allow(async_fn_in_trait)]
1077pub trait MediaStoreIntegrationTests {
1078    /// Test media content storage.
1079    async fn test_media_content(&self);
1080
1081    /// Test replacing a MXID.
1082    async fn test_replace_media_key(&self);
1083}
1084
1085impl<Store> MediaStoreIntegrationTests for Store
1086where
1087    Store: MediaStore + std::fmt::Debug,
1088{
1089    async fn test_media_content(&self) {
1090        let uri = mxc_uri!("mxc://localhost/media");
1091        let request_file = MediaRequestParameters {
1092            source: MediaSource::Plain(uri.to_owned()),
1093            format: MediaFormat::File,
1094        };
1095        let request_thumbnail = MediaRequestParameters {
1096            source: MediaSource::Plain(uri.to_owned()),
1097            format: MediaFormat::Thumbnail(MediaThumbnailSettings::with_method(
1098                Method::Crop,
1099                uint!(100),
1100                uint!(100),
1101            )),
1102        };
1103
1104        let other_uri = mxc_uri!("mxc://localhost/media-other");
1105        let request_other_file = MediaRequestParameters {
1106            source: MediaSource::Plain(other_uri.to_owned()),
1107            format: MediaFormat::File,
1108        };
1109
1110        let content: Vec<u8> = "hello".into();
1111        let thumbnail_content: Vec<u8> = "world".into();
1112        let other_content: Vec<u8> = "foo".into();
1113
1114        // Media isn't present in the cache.
1115        assert!(
1116            self.get_media_content(&request_file).await.unwrap().is_none(),
1117            "unexpected media found"
1118        );
1119        assert!(
1120            self.get_media_content(&request_thumbnail).await.unwrap().is_none(),
1121            "media not found"
1122        );
1123
1124        // Let's add the media.
1125        self.add_media_content(&request_file, content.clone(), IgnoreMediaRetentionPolicy::No)
1126            .await
1127            .expect("adding media failed");
1128
1129        // Media is present in the cache.
1130        assert_eq!(
1131            self.get_media_content(&request_file).await.unwrap().as_ref(),
1132            Some(&content),
1133            "media not found though added"
1134        );
1135
1136        // Let's remove the media.
1137        self.remove_media_content(&request_file).await.expect("removing media failed");
1138
1139        // Media isn't present in the cache.
1140        assert!(
1141            self.get_media_content(&request_file).await.unwrap().is_none(),
1142            "media still there after removing"
1143        );
1144
1145        // Let's add the media again.
1146        self.add_media_content(&request_file, content.clone(), IgnoreMediaRetentionPolicy::No)
1147            .await
1148            .expect("adding media again failed");
1149
1150        assert_eq!(
1151            self.get_media_content(&request_file).await.unwrap().as_ref(),
1152            Some(&content),
1153            "media not found after adding again"
1154        );
1155
1156        // Let's add the thumbnail media.
1157        self.add_media_content(
1158            &request_thumbnail,
1159            thumbnail_content.clone(),
1160            IgnoreMediaRetentionPolicy::No,
1161        )
1162        .await
1163        .expect("adding thumbnail failed");
1164
1165        // Media's thumbnail is present.
1166        assert_eq!(
1167            self.get_media_content(&request_thumbnail).await.unwrap().as_ref(),
1168            Some(&thumbnail_content),
1169            "thumbnail not found"
1170        );
1171
1172        // Let's add another media with a different URI.
1173        self.add_media_content(
1174            &request_other_file,
1175            other_content.clone(),
1176            IgnoreMediaRetentionPolicy::No,
1177        )
1178        .await
1179        .expect("adding other media failed");
1180
1181        // Other file is present.
1182        assert_eq!(
1183            self.get_media_content(&request_other_file).await.unwrap().as_ref(),
1184            Some(&other_content),
1185            "other file not found"
1186        );
1187
1188        // Let's remove media based on URI.
1189        self.remove_media_content_for_uri(uri).await.expect("removing all media for uri failed");
1190
1191        assert!(
1192            self.get_media_content(&request_file).await.unwrap().is_none(),
1193            "media wasn't removed"
1194        );
1195        assert!(
1196            self.get_media_content(&request_thumbnail).await.unwrap().is_none(),
1197            "thumbnail wasn't removed"
1198        );
1199        assert!(
1200            self.get_media_content(&request_other_file).await.unwrap().is_some(),
1201            "other media was removed"
1202        );
1203    }
1204
1205    async fn test_replace_media_key(&self) {
1206        let uri = mxc_uri!("mxc://sendqueue.local/tr4n-s4ct-10n1-d");
1207        let req = MediaRequestParameters {
1208            source: MediaSource::Plain(uri.to_owned()),
1209            format: MediaFormat::File,
1210        };
1211
1212        let content = "hello".as_bytes().to_owned();
1213
1214        // Media isn't present in the cache.
1215        assert!(self.get_media_content(&req).await.unwrap().is_none(), "unexpected media found");
1216
1217        // Add the media.
1218        self.add_media_content(&req, content.clone(), IgnoreMediaRetentionPolicy::No)
1219            .await
1220            .expect("adding media failed");
1221
1222        // Sanity-check: media is found after adding it.
1223        assert_eq!(self.get_media_content(&req).await.unwrap().unwrap(), b"hello");
1224
1225        // Replacing a media request works.
1226        let new_uri = mxc_uri!("mxc://matrix.org/tr4n-s4ct-10n1-d");
1227        let new_req = MediaRequestParameters {
1228            source: MediaSource::Plain(new_uri.to_owned()),
1229            format: MediaFormat::File,
1230        };
1231        self.replace_media_key(&req, &new_req)
1232            .await
1233            .expect("replacing the media request key failed");
1234
1235        // Finding with the previous request doesn't work anymore.
1236        assert!(
1237            self.get_media_content(&req).await.unwrap().is_none(),
1238            "unexpected media found with the old key"
1239        );
1240
1241        // Finding with the new request does work.
1242        assert_eq!(self.get_media_content(&new_req).await.unwrap().unwrap(), b"hello");
1243    }
1244}
1245
1246/// Macro building to allow your [`MediaStore`] implementation to run the entire
1247/// tests suite locally.
1248///
1249/// You need to provide an
1250/// `async fn get_media_store() -> media::store::Result<Store>` that provides a
1251/// fresh media store that implements `MediaStoreInner` on the same level you
1252/// invoke the macro.
1253///
1254/// ## Usage example
1255///
1256/// ```no_run
1257/// # use matrix_sdk_base::media::store::{
1258/// #    MediaStore,
1259/// #    MemoryMediaStore as MyStore,
1260/// #    Result as MediaStoreResult,
1261/// # };
1262///
1263/// #[cfg(test)]
1264/// mod tests {
1265///     use super::{MediaStoreResult, MyStore};
1266///
1267///     async fn get_media_store() -> MediaStoreResult<MyStore> {
1268///         Ok(MyStore::new())
1269///     }
1270///
1271///     media_store_integration_tests!();
1272/// }
1273/// ```
1274#[allow(unused_macros, unused_extern_crates)]
1275#[macro_export]
1276macro_rules! media_store_integration_tests {
1277    () => {
1278        mod media_store_integration_tests {
1279            use matrix_sdk_test::async_test;
1280            use $crate::media::store::integration_tests::MediaStoreIntegrationTests;
1281
1282            use super::get_media_store;
1283
1284            #[async_test]
1285            async fn test_media_content() {
1286                let media_store = get_media_store().await.unwrap();
1287                media_store.test_media_content().await;
1288            }
1289
1290            #[async_test]
1291            async fn test_replace_media_key() {
1292                let media_store = get_media_store().await.unwrap();
1293                media_store.test_replace_media_key().await;
1294            }
1295        }
1296    };
1297}
1298
1299/// Macro generating tests for the media store, related to time (mostly for the
1300/// cross-process lock).
1301#[allow(unused_macros)]
1302#[macro_export]
1303macro_rules! media_store_integration_tests_time {
1304    () => {
1305        mod media_store_integration_tests_time {
1306            use std::time::Duration;
1307
1308            #[cfg(all(target_family = "wasm", target_os = "unknown"))]
1309            use gloo_timers::future::sleep;
1310            use matrix_sdk_test::async_test;
1311            #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
1312            use tokio::time::sleep;
1313            use $crate::media::store::MediaStore;
1314
1315            use super::get_media_store;
1316
1317            #[async_test]
1318            async fn test_lease_locks() {
1319                let store = get_media_store().await.unwrap();
1320
1321                let acquired0 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
1322                assert_eq!(acquired0, Some(1)); // first lock generation
1323
1324                // Should extend the lease automatically (same holder).
1325                let acquired2 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
1326                assert_eq!(acquired2, Some(1)); // same lock generation
1327
1328                // Should extend the lease automatically (same holder + time is
1329                // ok).
1330                let acquired3 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
1331                assert_eq!(acquired3, Some(1)); // same lock generation
1332
1333                // Another attempt at taking the lock should fail, because it's
1334                // taken.
1335                let acquired4 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1336                assert!(acquired4.is_none()); // not acquired
1337
1338                // Even if we insist.
1339                let acquired5 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1340                assert!(acquired5.is_none()); // not acquired
1341
1342                // That's a nice test we got here, go take a little nap.
1343                sleep(Duration::from_millis(50)).await;
1344
1345                // Still too early.
1346                let acquired55 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1347                assert!(acquired55.is_none()); // not acquired
1348
1349                // Ok you can take another nap then.
1350                sleep(Duration::from_millis(250)).await;
1351
1352                // At some point, we do get the lock.
1353                let acquired6 = store.try_take_leased_lock(0, "key", "bob").await.unwrap();
1354                assert_eq!(acquired6, Some(2)); // new lock generation!
1355
1356                sleep(Duration::from_millis(1)).await;
1357
1358                // The other gets it almost immediately too.
1359                let acquired7 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
1360                assert_eq!(acquired7, Some(3)); // new lock generation!
1361
1362                sleep(Duration::from_millis(1)).await;
1363
1364                // But when we take a longer lease…
1365                let acquired8 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1366                assert_eq!(acquired8, Some(4)); // new lock generation!
1367
1368                // It blocks the other user.
1369                let acquired9 = store.try_take_leased_lock(300, "key", "alice").await.unwrap();
1370                assert!(acquired9.is_none()); // not acquired
1371
1372                // We can hold onto our lease.
1373                let acquired10 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
1374                assert_eq!(acquired10, Some(4)); // same lock generation
1375            }
1376        }
1377    };
1378}