atagen

macltok

public
Star0
Watch0
fe4133dbcd47e9263b8915f2406de5b2cab7e65a
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
tests/media.rsRust868 lines27.0 KB
1
mod support;
2
3
use std::{sync::Arc, time::Duration};
4
5
use macltok::{
6
    AttachmentUpload, ClientHandle, Engine, EngineConfig, EngineError, ErrorKind, Event,
7
    EventStream, MediaId, MediaSize, UploadDetails, UploadThumbnail,
8
};
9
use matrix_sdk::{
10
    media::{MediaFormat, MediaRequestParameters},
11
    ruma::{
12
        api::client::sync::sync_events::v5,
13
        event_id,
14
        events::room::{
15
            EncryptedFile, MediaSource,
16
            message::{
17
                AudioMessageEventContent, FileInfo, FileMessageEventContent,
18
                ImageMessageEventContent, MessageType, RoomMessageEventContent,
19
            },
20
        },
21
        media::Method,
22
        mxc_uri, room_id,
23
    },
24
    test_utils::mocks::MatrixMockServer,
25
};
26
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, event_factory::EventFactory};
27
use serde_json::{Value, json};
28
use wiremock::{
29
    Mock, ResponseTemplate,
30
    matchers::{method, path_regex},
31
};
32
33
struct MediaHarness {
34
    server: MatrixMockServer,
35
    client: matrix_sdk::Client,
36
    handle: ClientHandle,
37
    events: EventStream,
38
    running: tokio::task::JoinHandle<Result<macltok::Termination, EngineError>>,
39
    media_id: MediaId,
40
}
41
42
#[tokio::test]
43
async fn full_size_uses_full_source() {
44
    let mut harness = start(file_with_thumbnail()).await;
45
    mount_downloads(&harness.server).await;
46
47
    let bytes = fetch(&harness, MediaSize::Full).await;
48
    assert_eq!(&*bytes, b"binaryjpegfullimagedata");
49
    assert!(
50
        media_paths(&harness.server)
51
            .await
52
            .iter()
53
            .any(|path| path.ends_with("/localhost/full"))
54
    );
55
56
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
57
}
58
59
#[tokio::test]
60
async fn encrypted_media_is_cached_as_plaintext_like_the_sdk_does() {
61
    use std::io::Read as _;
62
63
    let plaintext = b"very secret image".to_vec();
64
    let mut cursor = std::io::Cursor::new(plaintext.clone());
65
    let mut encryptor = matrix_sdk_crypto::AttachmentEncryptor::new(&mut cursor);
66
    let mut ciphertext = Vec::new();
67
    encryptor.read_to_end(&mut ciphertext).unwrap();
68
    let keys = encryptor.finish();
69
    let file = EncryptedFile::new(
70
        mxc_uri!("mxc://localhost/secret").to_owned(),
71
        keys.encryption_info,
72
        keys.hashes,
73
    );
74
    let source = MediaSource::Encrypted(Box::new(file.clone()));
75
    let mut harness = start(RoomMessageEventContent::new(MessageType::Image(
76
        ImageMessageEventContent::encrypted("secret.png".to_owned(), file),
77
    )))
78
    .await;
79
    Mock::given(method("GET"))
80
        .and(path_regex("/download/localhost/secret$"))
81
        .respond_with(ResponseTemplate::new(200).set_body_bytes(ciphertext))
82
        .mount(harness.server.server())
83
        .await;
84
85
    let from_sdk = harness
86
        .client
87
        .media()
88
        .get_media_content(
89
            &MediaRequestParameters {
90
                source,
91
                format: MediaFormat::File,
92
            },
93
            true,
94
        )
95
        .await
96
        .unwrap();
97
    assert_eq!(from_sdk, plaintext);
98
    assert_eq!(&*fetch(&harness, MediaSize::Full).await, &plaintext[..]);
99
    assert_eq!(media_paths(&harness.server).await.len(), 1);
100
101
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
102
}
103
104
#[tokio::test]
105
async fn a_plain_event_cannot_poison_an_encrypted_files_cache() {
106
    use std::io::Read as _;
107
108
    let plaintext = b"very secret image".to_vec();
109
    let mut cursor = std::io::Cursor::new(plaintext.clone());
110
    let mut encryptor = matrix_sdk_crypto::AttachmentEncryptor::new(&mut cursor);
111
    let mut ciphertext = Vec::new();
112
    encryptor.read_to_end(&mut ciphertext).unwrap();
113
    let keys = encryptor.finish();
114
    let uri = mxc_uri!("mxc://localhost/secret").to_owned();
115
    let file = EncryptedFile::new(uri.clone(), keys.encryption_info, keys.hashes);
116
    let mut harness = start(RoomMessageEventContent::new(MessageType::Image(
117
        ImageMessageEventContent::plain("bait.png".to_owned(), uri),
118
    )))
119
    .await;
120
    Mock::given(method("GET"))
121
        .and(path_regex("/download/localhost/secret$"))
122
        .respond_with(ResponseTemplate::new(200).set_body_bytes(ciphertext.clone()))
123
        .mount(harness.server.server())
124
        .await;
125
    assert_eq!(&*fetch(&harness, MediaSize::Full).await, &ciphertext[..]);
126
127
    harness
128
        .server
129
        .sync_room(
130
            &harness.client,
131
            JoinedRoomBuilder::new(room_id!("!media:localhost")).add_timeline_event(
132
                EventFactory::new()
133
                    .event(RoomMessageEventContent::new(MessageType::Image(
134
                        ImageMessageEventContent::encrypted("secret.png".to_owned(), file),
135
                    )))
136
                    .sender(&ALICE),
137
            ),
138
        )
139
        .await;
140
    let event = support::receive_until(&mut harness.events, |event| {
141
        support::event_messages(event).into_iter().any(|message| {
142
            support::media_of(message).is_some_and(|media| media.filename == "secret.png")
143
        })
144
    })
145
    .await;
146
    let media_id = support::event_messages(&event)
147
        .into_iter()
148
        .find_map(|message| {
149
            support::media_of(message).filter(|media| media.filename == "secret.png")
150
        })
151
        .map(|media| media.id.clone())
152
        .unwrap();
153
    let media = harness
154
        .handle
155
        .media(&media_id, MediaSize::Full)
156
        .await
157
        .unwrap();
158
    assert_eq!(&*media.bytes, &plaintext[..]);
159
160
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
161
}
162
163
#[tokio::test]
164
async fn simultaneous_fetches_share_one_download() {
165
    let mut harness = start(plain_image()).await;
166
    Mock::given(method("GET"))
167
        .and(path_regex("/download/localhost/image$"))
168
        .respond_with(
169
            ResponseTemplate::new(200)
170
                .set_body_bytes(b"shared image bytes".to_vec())
171
                .set_delay(Duration::from_millis(300)),
172
        )
173
        .mount(harness.server.server())
174
        .await;
175
176
    let (first, second) = tokio::time::timeout(Duration::from_secs(5), async {
177
        tokio::join!(
178
            harness.handle.media(&harness.media_id, MediaSize::Full),
179
            harness.handle.media(&harness.media_id, MediaSize::Full),
180
        )
181
    })
182
    .await
183
    .expect("both fetches did not complete");
184
    assert_eq!(&*first.unwrap().bytes, b"shared image bytes");
185
    assert_eq!(&*second.unwrap().bytes, b"shared image bytes");
186
187
    let downloads = harness
188
        .server
189
        .received_requests()
190
        .await
191
        .unwrap()
192
        .iter()
193
        .filter(|request| request.url.path().ends_with("/download/localhost/image"))
194
        .count();
195
    assert_eq!(downloads, 1, "duplicate in-flight media download");
196
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
197
}
198
199
#[tokio::test]
200
async fn downloaded_media_over_limit_is_rejected() {
201
    let mut harness = start_with_config(
202
        plain_image(),
203
        EngineConfig::default()
204
            .with_initial_history_limit(0)
205
            .with_max_media_bytes(4),
206
    )
207
    .await;
208
    Mock::given(method("GET"))
209
        .and(path_regex("/download/localhost/image$"))
210
        .respond_with(ResponseTemplate::new(200).set_body_bytes(b"too large".to_vec()))
211
        .mount(harness.server.server())
212
        .await;
213
214
    let error = harness
215
        .handle
216
        .media(&harness.media_id, MediaSize::Full)
217
        .await
218
        .unwrap_err();
219
    assert_eq!(error.kind, ErrorKind::MediaTooLarge);
220
221
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
222
}
223
224
#[tokio::test]
225
async fn thumbnail_size_prefers_explicit_thumbnail() {
226
    let mut harness = start(file_with_thumbnail()).await;
227
    mount_downloads(&harness.server).await;
228
229
    let bytes = fetch(&harness, thumbnail_size()).await;
230
    assert_eq!(&*bytes, b"binaryjpegfullimagedata");
231
    let paths = media_paths(&harness.server).await;
232
    assert!(
233
        paths
234
            .iter()
235
            .any(|path| path.ends_with("/localhost/explicit-thumbnail"))
236
    );
237
    assert!(!paths.iter().any(|path| path.ends_with("/localhost/full")));
238
239
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
240
}
241
242
#[tokio::test]
243
async fn plain_image_thumbnail_uses_thumbnail_endpoint() {
244
    let mut harness = start(plain_image()).await;
245
    harness
246
        .server
247
        .mock_media_thumbnail(Method::Scale, 800, 800, false)
248
        .ok()
249
        .mount()
250
        .await;
251
    harness
252
        .server
253
        .mock_authed_media_thumbnail(Method::Scale, 800, 800, false)
254
        .ok()
255
        .mount()
256
        .await;
257
258
    let bytes = fetch(&harness, thumbnail_size()).await;
259
    assert_eq!(&*bytes, b"binaryjpegthumbnaildata");
260
    assert!(
261
        media_paths(&harness.server)
262
            .await
263
            .iter()
264
            .any(|path| path.contains("/thumbnail/localhost/image"))
265
    );
266
267
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
268
}
269
270
#[tokio::test]
271
async fn audio_thumbnail_fetch_fails_with_media_unavailable() {
272
    let mut harness = start(plain_audio()).await;
273
274
    let error = harness
275
        .handle
276
        .media(&harness.media_id, thumbnail_size())
277
        .await
278
        .unwrap_err();
279
    assert_eq!(error.kind, ErrorKind::MediaUnavailable);
280
281
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
282
}
283
284
#[tokio::test]
285
async fn media_upload_is_queued_and_sent() {
286
    let mut harness = start(plain_image()).await;
287
    harness
288
        .server
289
        .mock_authenticated_media_config()
290
        .ok_default()
291
        .mount()
292
        .await;
293
    harness
294
        .server
295
        .mock_upload()
296
        .expect_mime_type("image/jpeg")
297
        .ok(mxc_uri!("mxc://localhost/uploaded"))
298
        .expect(1)
299
        .mount()
300
        .await;
301
    harness
302
        .server
303
        .mock_room_send()
304
        .ok(event_id!("$uploaded:localhost"))
305
        .expect(1)
306
        .mount()
307
        .await;
308
309
    let mut upload = AttachmentUpload::new(
310
        room_id!("!media:localhost").to_owned(),
311
        "photo.jpg",
312
        "image/jpeg",
313
        b"photo data".to_vec(),
314
    );
315
    upload.caption = Some("holiday".to_owned());
316
    harness.handle.upload(upload).await.unwrap();
317
318
    tokio::time::timeout(Duration::from_secs(5), async {
319
        loop {
320
            let requests = harness.server.received_requests().await.unwrap();
321
            if requests.iter().any(|request| {
322
                request.url.path().contains("/send/m.room.message/")
323
                    && String::from_utf8_lossy(&request.body).contains("mxc://localhost/uploaded")
324
            }) {
325
                break;
326
            }
327
            tokio::task::yield_now().await;
328
        }
329
    })
330
    .await
331
    .expect("queued media was not sent");
332
333
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
334
}
335
336
#[tokio::test]
337
async fn queued_offline_attachment_exposes_fetchable_local_media() {
338
    let mut harness = start(plain_image()).await;
339
    harness.client.send_queue().set_enabled(false).await;
340
    let original = b"offline image bytes".to_vec();
341
    let mut upload = AttachmentUpload::new(
342
        room_id!("!media:localhost").to_owned(),
343
        "pending.png",
344
        "image/png",
345
        original.clone(),
346
    );
347
    upload.details = Some(UploadDetails::Image {
348
        width: Some(32),
349
        height: Some(16),
350
        blurhash: None,
351
    });
352
    harness.handle.upload(upload).await.unwrap();
353
    let event = support::receive_until(&mut harness.events, |event| {
354
        support::event_messages(event).into_iter().any(|event| {
355
            event.own
356
                && event.transaction_id.is_some()
357
                && support::media_of(event).is_some_and(|media| media.filename == "pending.png")
358
        })
359
    })
360
    .await;
361
    let media = support::event_messages(&event)
362
        .into_iter()
363
        .find(|event| event.own && support::media_of(event).is_some())
364
        .and_then(support::media_of)
365
        .unwrap();
366
    assert_eq!((media.width, media.height), (Some(32), Some(16)));
367
    assert_eq!(media.size_bytes, Some(original.len() as u64));
368
    let media_id = media.id.clone();
369
370
    let fetched = harness
371
        .handle
372
        .media(&media_id, MediaSize::Full)
373
        .await
374
        .unwrap();
375
    assert_eq!(&*fetched.bytes, &original[..]);
376
377
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
378
}
379
380
#[tokio::test]
381
async fn image_upload_uses_caller_supplied_metadata_and_thumbnail() {
382
    let mut harness = start(plain_image()).await;
383
    harness
384
        .server
385
        .mock_authenticated_media_config()
386
        .ok_default()
387
        .mount()
388
        .await;
389
    harness
390
        .server
391
        .mock_upload()
392
        .expect_mime_type("image/png")
393
        .respond_with(|request: &wiremock::Request| {
394
            let uri = if request.body == b"thumbnail bytes" {
395
                "mxc://localhost/image-thumb"
396
            } else {
397
                "mxc://localhost/image-upload"
398
            };
399
            ResponseTemplate::new(200).set_body_json(json!({ "content_uri": uri }))
400
        })
401
        .expect(2)
402
        .mount()
403
        .await;
404
    harness
405
        .server
406
        .mock_room_send()
407
        .ok(event_id!("$image-upload:localhost"))
408
        .expect(1)
409
        .mount()
410
        .await;
411
412
    let original = b"image bytes".to_vec();
413
    let thumbnail_bytes = b"thumbnail bytes".to_vec();
414
    let mut upload = AttachmentUpload::new(
415
        room_id!("!media:localhost").to_owned(),
416
        "large.png",
417
        "image/png",
418
        original.clone(),
419
    );
420
    upload.details = Some(UploadDetails::Image {
421
        width: Some(900),
422
        height: Some(450),
423
        blurhash: None,
424
    });
425
    upload.thumbnail = Some(UploadThumbnail::new(
426
        thumbnail_bytes.clone(),
427
        "image/png",
428
        800,
429
        400,
430
    ));
431
    harness.handle.upload(upload).await.unwrap();
432
433
    let content = uploaded_content(&harness.server).await;
434
    assert_eq!(content["msgtype"], "m.image");
435
    assert_eq!(content["info"]["w"], 900);
436
    assert_eq!(content["info"]["h"], 450);
437
    assert_eq!(content["info"]["size"], original.len());
438
    assert_eq!(content["info"]["thumbnail_info"]["w"], 800);
439
    assert_eq!(content["info"]["thumbnail_info"]["h"], 400);
440
    assert_eq!(content["info"]["thumbnail_info"]["mimetype"], "image/png");
441
    assert_eq!(
442
        content["info"]["thumbnail_info"]["size"],
443
        thumbnail_bytes.len()
444
    );
445
    assert_eq!(
446
        content["info"]["thumbnail_url"],
447
        "mxc://localhost/image-thumb"
448
    );
449
    assert_eq!(content["url"], "mxc://localhost/image-upload");
450
451
    let requests = harness.server.received_requests().await.unwrap();
452
    let uploads = requests
453
        .iter()
454
        .filter(|request| request.url.path().ends_with("/_matrix/media/v3/upload"))
455
        .collect::<Vec<_>>();
456
    assert_eq!(uploads.len(), 2);
457
    assert!(uploads.iter().any(|request| request.body == original));
458
    assert!(
459
        uploads
460
            .iter()
461
            .any(|request| request.body == thumbnail_bytes)
462
    );
463
464
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
465
}
466
467
#[tokio::test]
468
async fn video_upload_preserves_metadata_and_supplied_thumbnail() {
469
    let mut harness = start(plain_image()).await;
470
    harness
471
        .server
472
        .mock_authenticated_media_config()
473
        .ok_default()
474
        .mount()
475
        .await;
476
    harness
477
        .server
478
        .mock_upload()
479
        .expect_mime_type("image/png")
480
        .ok(mxc_uri!("mxc://localhost/video-thumb"))
481
        .expect(1)
482
        .mount()
483
        .await;
484
    harness
485
        .server
486
        .mock_upload()
487
        .expect_mime_type("video/mp4")
488
        .ok(mxc_uri!("mxc://localhost/video-upload"))
489
        .expect(1)
490
        .mount()
491
        .await;
492
    harness
493
        .server
494
        .mock_room_send()
495
        .ok(event_id!("$video-upload:localhost"))
496
        .expect(1)
497
        .mount()
498
        .await;
499
500
    let mut upload = AttachmentUpload::new(
501
        room_id!("!media:localhost").to_owned(),
502
        "clip.mp4",
503
        "video/mp4",
504
        b"video bytes".to_vec(),
505
    );
506
    upload.details = Some(UploadDetails::Video {
507
        width: Some(1920),
508
        height: Some(1080),
509
        duration_ms: Some(3500),
510
        blurhash: Some("blurhash".to_owned()),
511
    });
512
    upload.thumbnail = Some(UploadThumbnail::new(
513
        b"poster bytes".to_vec(),
514
        "image/png",
515
        320,
516
        180,
517
    ));
518
    harness.handle.upload(upload).await.unwrap();
519
520
    let content = uploaded_content(&harness.server).await;
521
    assert_eq!(content["msgtype"], "m.video");
522
    assert_eq!(content["info"]["w"], 1920);
523
    assert_eq!(content["info"]["h"], 1080);
524
    assert_eq!(content["info"]["duration"], 3500);
525
    assert_eq!(content["info"]["size"], 11);
526
    assert_eq!(content["info"]["thumbnail_info"]["w"], 320);
527
    assert_eq!(content["info"]["thumbnail_info"]["h"], 180);
528
    assert_eq!(content["info"]["thumbnail_info"]["size"], 12);
529
    assert_eq!(
530
        content["info"]["thumbnail_url"],
531
        "mxc://localhost/video-thumb"
532
    );
533
    assert_eq!(content["url"], "mxc://localhost/video-upload");
534
535
    let requests = harness.server.received_requests().await.unwrap();
536
    assert!(requests.iter().any(|request| {
537
        request.url.path().ends_with("/_matrix/media/v3/upload") && request.body == b"poster bytes"
538
    }));
539
540
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
541
}
542
543
#[tokio::test]
544
async fn voice_upload_emits_voice_details_and_correlated_progress() {
545
    let mut harness = start(plain_image()).await;
546
    harness
547
        .server
548
        .mock_authenticated_media_config()
549
        .ok_default()
550
        .mount()
551
        .await;
552
    harness
553
        .server
554
        .mock_upload()
555
        .expect_mime_type("audio/ogg")
556
        .respond_with(
557
            ResponseTemplate::new(200)
558
                .set_body_json(json!({ "content_uri": "mxc://localhost/voice-upload" }))
559
                .set_delay(Duration::from_millis(500)),
560
        )
561
        .expect(1)
562
        .mount()
563
        .await;
564
    harness
565
        .server
566
        .mock_room_send()
567
        .ok(event_id!("$voice-upload:localhost"))
568
        .expect(1)
569
        .mount()
570
        .await;
571
572
    let data = b"voice bytes".to_vec();
573
    let mut upload = AttachmentUpload::new(
574
        room_id!("!media:localhost").to_owned(),
575
        "voice.ogg",
576
        "audio/ogg",
577
        data.clone(),
578
    );
579
    upload.details = Some(UploadDetails::Voice {
580
        duration_ms: 4200,
581
        waveform: vec![0, 512, 1024],
582
    });
583
    harness.handle.upload(upload).await.unwrap();
584
585
    let pending = harness
586
        .handle
587
        .pending_send_ids(room_id!("!media:localhost"))
588
        .await
589
        .unwrap();
590
    assert_eq!(pending.len(), 1);
591
    let transaction_id = pending[0].clone();
592
    let progress = support::receive_until(&mut harness.events, |event| {
593
        matches!(event, Event::MediaUploadProgress { transaction_id: seen, complete: true, .. } if seen == &transaction_id)
594
    })
595
    .await;
596
    assert!(matches!(progress, Event::MediaUploadProgress {
597
        room_id,
598
        transaction_id: seen,
599
        uploaded_bytes,
600
        total_bytes,
601
        complete: true,
602
    } if room_id == room_id!("!media:localhost") && seen == transaction_id && uploaded_bytes == data.len() as u64 && total_bytes == data.len() as u64));
603
604
    let content = uploaded_content(&harness.server).await;
605
    assert_eq!(content["msgtype"], "m.audio");
606
    assert_eq!(content["info"]["duration"], 4200);
607
    assert_eq!(content["info"]["size"], data.len());
608
    assert_eq!(content["org.matrix.msc1767.audio"]["duration"], 4200);
609
    assert_eq!(
610
        content["org.matrix.msc1767.audio"]["waveform"],
611
        json!([0, 512, 1024])
612
    );
613
    assert_eq!(content["org.matrix.msc3245.voice"], json!({}));
614
615
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
616
}
617
618
#[tokio::test]
619
async fn mismatched_upload_metadata_and_invalid_waveform_fail_without_uploading() {
620
    let mut harness = start(plain_image()).await;
621
    let mut mismatched = AttachmentUpload::new(
622
        room_id!("!media:localhost").to_owned(),
623
        "clip.mp4",
624
        "video/mp4",
625
        b"video bytes".to_vec(),
626
    );
627
    mismatched.details = Some(UploadDetails::Image {
628
        width: Some(32),
629
        height: Some(32),
630
        blurhash: None,
631
    });
632
    let error = harness.handle.upload(mismatched).await.unwrap_err();
633
    assert_eq!(error.kind, ErrorKind::Invalid);
634
635
    let mut voice = AttachmentUpload::new(
636
        room_id!("!media:localhost").to_owned(),
637
        "voice.ogg",
638
        "audio/ogg",
639
        b"voice bytes".to_vec(),
640
    );
641
    voice.details = Some(UploadDetails::Voice {
642
        duration_ms: 1000,
643
        waveform: vec![1025],
644
    });
645
    let error = harness.handle.upload(voice).await.unwrap_err();
646
    assert_eq!(error.kind, ErrorKind::Invalid);
647
648
    assert!(
649
        !harness
650
            .server
651
            .received_requests()
652
            .await
653
            .unwrap()
654
            .iter()
655
            .any(|request| { request.url.path().ends_with("/_matrix/media/v3/upload") })
656
    );
657
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
658
}
659
660
#[tokio::test]
661
async fn upload_over_media_limit_fails_without_uploading() {
662
    let mut harness = start_with_config(
663
        plain_image(),
664
        EngineConfig::default()
665
            .with_initial_history_limit(0)
666
            .with_max_media_bytes(4),
667
    )
668
    .await;
669
    let upload = AttachmentUpload::new(
670
        room_id!("!media:localhost").to_owned(),
671
        "large.bin",
672
        "application/octet-stream",
673
        b"too large".to_vec(),
674
    );
675
    let error = harness.handle.upload(upload).await.unwrap_err();
676
    assert_eq!(error.kind, ErrorKind::MediaTooLarge);
677
    assert!(
678
        !harness
679
            .server
680
            .received_requests()
681
            .await
682
            .unwrap()
683
            .iter()
684
            .any(|request| { request.url.path().ends_with("/_matrix/media/v3/upload") })
685
    );
686
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
687
}
688
689
#[tokio::test]
690
async fn audio_thumbnail_fails_without_uploading() {
691
    let mut harness = start(plain_image()).await;
692
    let mut upload = AttachmentUpload::new(
693
        room_id!("!media:localhost").to_owned(),
694
        "voice.ogg",
695
        "audio/ogg",
696
        b"voice bytes".to_vec(),
697
    );
698
    upload.thumbnail = Some(UploadThumbnail::new(
699
        b"thumbnail".to_vec(),
700
        "image/png",
701
        80,
702
        80,
703
    ));
704
    let error = harness.handle.upload(upload).await.unwrap_err();
705
    assert_eq!(error.kind, ErrorKind::Invalid);
706
    assert!(
707
        !harness
708
            .server
709
            .received_requests()
710
            .await
711
            .unwrap()
712
            .iter()
713
            .any(|request| { request.url.path().ends_with("/_matrix/media/v3/upload") })
714
    );
715
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
716
}
717
718
#[tokio::test]
719
async fn invalid_upload_mime_fails_without_uploading() {
720
    let mut harness = start(plain_image()).await;
721
    let upload = AttachmentUpload::new(
722
        room_id!("!media:localhost").to_owned(),
723
        "photo.jpg",
724
        "not a MIME type",
725
        b"photo data".to_vec(),
726
    );
727
    let error = harness.handle.upload(upload).await.unwrap_err();
728
    assert_eq!(error.kind, ErrorKind::Invalid);
729
730
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
731
}
732
733
async fn start(content: RoomMessageEventContent) -> MediaHarness {
734
    start_with_config(
735
        content,
736
        EngineConfig::default().with_initial_history_limit(0),
737
    )
738
    .await
739
}
740
741
async fn start_with_config(content: RoomMessageEventContent, config: EngineConfig) -> MediaHarness {
742
    let server = MatrixMockServer::new().await;
743
    server
744
        .mock_sliding_sync()
745
        .ok(v5::Response::new("1".to_owned()))
746
        .mount()
747
        .await;
748
    let client = server.client_builder().build().await;
749
    let room_id = room_id!("!media:localhost");
750
    server.mock_room_state_encryption().plain().mount().await;
751
    server.sync_joined_room(&client, room_id).await;
752
    let sync_client = client.clone();
753
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
754
    let running = tokio::spawn(engine.run());
755
756
    handle.open_room(room_id.to_owned()).await.unwrap();
757
    support::receive_until(
758
        &mut events,
759
        |event| matches!(event, Event::TimelineReset { timeline_id: reset, .. } if reset.room_id() == room_id),
760
    )
761
    .await;
762
763
    server
764
        .sync_room(
765
            &sync_client,
766
            JoinedRoomBuilder::new(room_id)
767
                .add_timeline_event(EventFactory::new().event(content).sender(&ALICE)),
768
        )
769
        .await;
770
    let event = support::receive_until(&mut events, |event| {
771
        support::event_messages(event)
772
            .into_iter()
773
            .any(|event| support::media_of(event).is_some())
774
    })
775
    .await;
776
    let media_id = support::event_messages(&event)
777
        .into_iter()
778
        .find_map(|event| support::media_of(event).map(|media| media.id.clone()))
779
        .unwrap();
780
781
    MediaHarness {
782
        server,
783
        client: sync_client,
784
        handle,
785
        events,
786
        running,
787
        media_id,
788
    }
789
}
790
791
async fn mount_downloads(server: &MatrixMockServer) {
792
    server.mock_media_download().ok_image().mount().await;
793
    server.mock_authed_media_download().ok_image().mount().await;
794
}
795
796
fn thumbnail_size() -> MediaSize {
797
    MediaSize::Thumbnail {
798
        width: 800,
799
        height: 800,
800
    }
801
}
802
803
async fn fetch(harness: &MediaHarness, size: MediaSize) -> Arc<[u8]> {
804
    harness
805
        .handle
806
        .media(&harness.media_id, size)
807
        .await
808
        .unwrap()
809
        .bytes
810
}
811
812
async fn media_paths(server: &MatrixMockServer) -> Vec<String> {
813
    server
814
        .received_requests()
815
        .await
816
        .unwrap()
817
        .into_iter()
818
        .map(|request| request.url.path().to_owned())
819
        .filter(|path| path.contains("/media/"))
820
        .collect()
821
}
822
823
async fn uploaded_content(server: &MatrixMockServer) -> Value {
824
    tokio::time::timeout(Duration::from_secs(5), async {
825
        loop {
826
            if let Some(request) = server
827
                .received_requests()
828
                .await
829
                .unwrap()
830
                .iter()
831
                .find(|request| request.url.path().contains("/send/m.room.message/"))
832
            {
833
                return serde_json::from_slice(&request.body).unwrap();
834
            }
835
            tokio::task::yield_now().await;
836
        }
837
    })
838
    .await
839
    .expect("queued media was not sent")
840
}
841
842
fn file_with_thumbnail() -> RoomMessageEventContent {
843
    let mut info = FileInfo::new();
844
    info.thumbnail_source = Some(MediaSource::Plain(
845
        mxc_uri!("mxc://localhost/explicit-thumbnail").to_owned(),
846
    ));
847
    RoomMessageEventContent::new(MessageType::File(
848
        FileMessageEventContent::plain(
849
            "file.bin".to_owned(),
850
            mxc_uri!("mxc://localhost/full").to_owned(),
851
        )
852
        .info(Box::new(info)),
853
    ))
854
}
855
856
fn plain_image() -> RoomMessageEventContent {
857
    RoomMessageEventContent::new(MessageType::Image(ImageMessageEventContent::plain(
858
        "image.jpg".to_owned(),
859
        mxc_uri!("mxc://localhost/image").to_owned(),
860
    )))
861
}
862
863
fn plain_audio() -> RoomMessageEventContent {
864
    RoomMessageEventContent::new(MessageType::Audio(AudioMessageEventContent::plain(
865
        "audio.ogg".to_owned(),
866
        mxc_uri!("mxc://localhost/audio").to_owned(),
867
    )))
868
}