atagen

macltok

public
Star0
Watch0
7b376d620a3d030cadb0437821b25ca7136e4995
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
tests/media.rsRust876 lines27.3 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, UploadOutcome, 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
    support::receive_until(&mut harness.events, |event| {
604
        matches!(event, Event::MediaUploadFinished {
605
            transaction_id: seen,
606
            outcome: UploadOutcome::Sent { event_id },
607
            ..
608
        } if seen == &transaction_id && event_id == event_id!("$voice-upload:localhost"))
609
    })
610
    .await;
611
612
    let content = uploaded_content(&harness.server).await;
613
    assert_eq!(content["msgtype"], "m.audio");
614
    assert_eq!(content["info"]["duration"], 4200);
615
    assert_eq!(content["info"]["size"], data.len());
616
    assert_eq!(content["org.matrix.msc1767.audio"]["duration"], 4200);
617
    assert_eq!(
618
        content["org.matrix.msc1767.audio"]["waveform"],
619
        json!([0, 512, 1024])
620
    );
621
    assert_eq!(content["org.matrix.msc3245.voice"], json!({}));
622
623
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
624
}
625
626
#[tokio::test]
627
async fn mismatched_upload_metadata_and_invalid_waveform_fail_without_uploading() {
628
    let mut harness = start(plain_image()).await;
629
    let mut mismatched = AttachmentUpload::new(
630
        room_id!("!media:localhost").to_owned(),
631
        "clip.mp4",
632
        "video/mp4",
633
        b"video bytes".to_vec(),
634
    );
635
    mismatched.details = Some(UploadDetails::Image {
636
        width: Some(32),
637
        height: Some(32),
638
        blurhash: None,
639
    });
640
    let error = harness.handle.upload(mismatched).await.unwrap_err();
641
    assert_eq!(error.kind, ErrorKind::Invalid);
642
643
    let mut voice = AttachmentUpload::new(
644
        room_id!("!media:localhost").to_owned(),
645
        "voice.ogg",
646
        "audio/ogg",
647
        b"voice bytes".to_vec(),
648
    );
649
    voice.details = Some(UploadDetails::Voice {
650
        duration_ms: 1000,
651
        waveform: vec![1025],
652
    });
653
    let error = harness.handle.upload(voice).await.unwrap_err();
654
    assert_eq!(error.kind, ErrorKind::Invalid);
655
656
    assert!(
657
        !harness
658
            .server
659
            .received_requests()
660
            .await
661
            .unwrap()
662
            .iter()
663
            .any(|request| { request.url.path().ends_with("/_matrix/media/v3/upload") })
664
    );
665
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
666
}
667
668
#[tokio::test]
669
async fn upload_over_media_limit_fails_without_uploading() {
670
    let mut harness = start_with_config(
671
        plain_image(),
672
        EngineConfig::default()
673
            .with_initial_history_limit(0)
674
            .with_max_media_bytes(4),
675
    )
676
    .await;
677
    let upload = AttachmentUpload::new(
678
        room_id!("!media:localhost").to_owned(),
679
        "large.bin",
680
        "application/octet-stream",
681
        b"too large".to_vec(),
682
    );
683
    let error = harness.handle.upload(upload).await.unwrap_err();
684
    assert_eq!(error.kind, ErrorKind::MediaTooLarge);
685
    assert!(
686
        !harness
687
            .server
688
            .received_requests()
689
            .await
690
            .unwrap()
691
            .iter()
692
            .any(|request| { request.url.path().ends_with("/_matrix/media/v3/upload") })
693
    );
694
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
695
}
696
697
#[tokio::test]
698
async fn audio_thumbnail_fails_without_uploading() {
699
    let mut harness = start(plain_image()).await;
700
    let mut upload = AttachmentUpload::new(
701
        room_id!("!media:localhost").to_owned(),
702
        "voice.ogg",
703
        "audio/ogg",
704
        b"voice bytes".to_vec(),
705
    );
706
    upload.thumbnail = Some(UploadThumbnail::new(
707
        b"thumbnail".to_vec(),
708
        "image/png",
709
        80,
710
        80,
711
    ));
712
    let error = harness.handle.upload(upload).await.unwrap_err();
713
    assert_eq!(error.kind, ErrorKind::Invalid);
714
    assert!(
715
        !harness
716
            .server
717
            .received_requests()
718
            .await
719
            .unwrap()
720
            .iter()
721
            .any(|request| { request.url.path().ends_with("/_matrix/media/v3/upload") })
722
    );
723
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
724
}
725
726
#[tokio::test]
727
async fn invalid_upload_mime_fails_without_uploading() {
728
    let mut harness = start(plain_image()).await;
729
    let upload = AttachmentUpload::new(
730
        room_id!("!media:localhost").to_owned(),
731
        "photo.jpg",
732
        "not a MIME type",
733
        b"photo data".to_vec(),
734
    );
735
    let error = harness.handle.upload(upload).await.unwrap_err();
736
    assert_eq!(error.kind, ErrorKind::Invalid);
737
738
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
739
}
740
741
async fn start(content: RoomMessageEventContent) -> MediaHarness {
742
    start_with_config(
743
        content,
744
        EngineConfig::default().with_initial_history_limit(0),
745
    )
746
    .await
747
}
748
749
async fn start_with_config(content: RoomMessageEventContent, config: EngineConfig) -> MediaHarness {
750
    let server = MatrixMockServer::new().await;
751
    server
752
        .mock_sliding_sync()
753
        .ok(v5::Response::new("1".to_owned()))
754
        .mount()
755
        .await;
756
    let client = server.client_builder().build().await;
757
    let room_id = room_id!("!media:localhost");
758
    server.mock_room_state_encryption().plain().mount().await;
759
    server.sync_joined_room(&client, room_id).await;
760
    let sync_client = client.clone();
761
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
762
    let running = tokio::spawn(engine.run());
763
764
    handle.open_room(room_id.to_owned()).await.unwrap();
765
    support::receive_until(
766
        &mut events,
767
        |event| matches!(event, Event::TimelineReset { timeline_id: reset, .. } if reset.room_id() == room_id),
768
    )
769
    .await;
770
771
    server
772
        .sync_room(
773
            &sync_client,
774
            JoinedRoomBuilder::new(room_id)
775
                .add_timeline_event(EventFactory::new().event(content).sender(&ALICE)),
776
        )
777
        .await;
778
    let event = support::receive_until(&mut events, |event| {
779
        support::event_messages(event)
780
            .into_iter()
781
            .any(|event| support::media_of(event).is_some())
782
    })
783
    .await;
784
    let media_id = support::event_messages(&event)
785
        .into_iter()
786
        .find_map(|event| support::media_of(event).map(|media| media.id.clone()))
787
        .unwrap();
788
789
    MediaHarness {
790
        server,
791
        client: sync_client,
792
        handle,
793
        events,
794
        running,
795
        media_id,
796
    }
797
}
798
799
async fn mount_downloads(server: &MatrixMockServer) {
800
    server.mock_media_download().ok_image().mount().await;
801
    server.mock_authed_media_download().ok_image().mount().await;
802
}
803
804
fn thumbnail_size() -> MediaSize {
805
    MediaSize::Thumbnail {
806
        width: 800,
807
        height: 800,
808
    }
809
}
810
811
async fn fetch(harness: &MediaHarness, size: MediaSize) -> Arc<[u8]> {
812
    harness
813
        .handle
814
        .media(&harness.media_id, size)
815
        .await
816
        .unwrap()
817
        .bytes
818
}
819
820
async fn media_paths(server: &MatrixMockServer) -> Vec<String> {
821
    server
822
        .received_requests()
823
        .await
824
        .unwrap()
825
        .into_iter()
826
        .map(|request| request.url.path().to_owned())
827
        .filter(|path| path.contains("/media/"))
828
        .collect()
829
}
830
831
async fn uploaded_content(server: &MatrixMockServer) -> Value {
832
    tokio::time::timeout(Duration::from_secs(5), async {
833
        loop {
834
            if let Some(request) = server
835
                .received_requests()
836
                .await
837
                .unwrap()
838
                .iter()
839
                .find(|request| request.url.path().contains("/send/m.room.message/"))
840
            {
841
                return serde_json::from_slice(&request.body).unwrap();
842
            }
843
            tokio::task::yield_now().await;
844
        }
845
    })
846
    .await
847
    .expect("queued media was not sent")
848
}
849
850
fn file_with_thumbnail() -> RoomMessageEventContent {
851
    let mut info = FileInfo::new();
852
    info.thumbnail_source = Some(MediaSource::Plain(
853
        mxc_uri!("mxc://localhost/explicit-thumbnail").to_owned(),
854
    ));
855
    RoomMessageEventContent::new(MessageType::File(
856
        FileMessageEventContent::plain(
857
            "file.bin".to_owned(),
858
            mxc_uri!("mxc://localhost/full").to_owned(),
859
        )
860
        .info(Box::new(info)),
861
    ))
862
}
863
864
fn plain_image() -> RoomMessageEventContent {
865
    RoomMessageEventContent::new(MessageType::Image(ImageMessageEventContent::plain(
866
        "image.jpg".to_owned(),
867
        mxc_uri!("mxc://localhost/image").to_owned(),
868
    )))
869
}
870
871
fn plain_audio() -> RoomMessageEventContent {
872
    RoomMessageEventContent::new(MessageType::Audio(AudioMessageEventContent::plain(
873
        "audio.ogg".to_owned(),
874
        mxc_uri!("mxc://localhost/audio").to_owned(),
875
    )))
876
}