atagen

macltok

public
Star0
Watch0
411bc1c78b65ac2d29c0a44b166993a7e76f1663
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: tests/media.rs

31ce2453c82fatagen2026-09-24 13:07 UTC1
mod support;
31ce2453c82fatagen2026-09-24 13:07 UTC2
96fdf90aacd6atagen2026-09-08 08:24 UTC3
use std::{sync::Arc, time::Duration};
96fdf90aacd6atagen2026-09-08 08:24 UTC4
96fdf90aacd6atagen2026-09-08 08:24 UTC5
use macltok::{
31ce2453c82fatagen2026-09-24 13:07 UTC6
    AttachmentUpload, ClientHandle, Engine, EngineConfig, EngineError, ErrorKind, Event,
5b21b919d1e6atagen2026-09-27 11:47 UTC7
    EventStream, MediaFile, MediaId, MediaLocation, MediaSize, UploadDetails, UploadOutcome,
5b21b919d1e6atagen2026-09-27 11:47 UTC8
    UploadThumbnail,
96fdf90aacd6atagen2026-09-08 08:24 UTC9
};
96fdf90aacd6atagen2026-09-08 08:24 UTC10
use matrix_sdk::{
fa9114fcf75fatagen2026-09-24 12:51 UTC11
    media::{MediaFormat, MediaRequestParameters},
96fdf90aacd6atagen2026-09-08 08:24 UTC12
    ruma::{
96fdf90aacd6atagen2026-09-08 08:24 UTC13
        api::client::sync::sync_events::v5,
96fdf90aacd6atagen2026-09-08 08:24 UTC14
        event_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC15
        events::room::{
fa9114fcf75fatagen2026-09-24 12:51 UTC16
            EncryptedFile, MediaSource,
96fdf90aacd6atagen2026-09-08 08:24 UTC17
            message::{
96fdf90aacd6atagen2026-09-08 08:24 UTC18
                AudioMessageEventContent, FileInfo, FileMessageEventContent,
96fdf90aacd6atagen2026-09-08 08:24 UTC19
                ImageMessageEventContent, MessageType, RoomMessageEventContent,
5b21b919d1e6atagen2026-09-27 11:47 UTC20
                VideoMessageEventContent,
96fdf90aacd6atagen2026-09-08 08:24 UTC21
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC22
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC23
        media::Method,
96fdf90aacd6atagen2026-09-08 08:24 UTC24
        mxc_uri, room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC25
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC26
    test_utils::mocks::MatrixMockServer,
96fdf90aacd6atagen2026-09-08 08:24 UTC27
};
96fdf90aacd6atagen2026-09-08 08:24 UTC28
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, event_factory::EventFactory};
96fdf90aacd6atagen2026-09-08 08:24 UTC29
use serde_json::{Value, json};
96fdf90aacd6atagen2026-09-08 08:24 UTC30
use wiremock::{
96fdf90aacd6atagen2026-09-08 08:24 UTC31
    Mock, ResponseTemplate,
96fdf90aacd6atagen2026-09-08 08:24 UTC32
    matchers::{method, path_regex},
96fdf90aacd6atagen2026-09-08 08:24 UTC33
};
96fdf90aacd6atagen2026-09-08 08:24 UTC34
96fdf90aacd6atagen2026-09-08 08:24 UTC35
struct MediaHarness {
96fdf90aacd6atagen2026-09-08 08:24 UTC36
    server: MatrixMockServer,
96fdf90aacd6atagen2026-09-08 08:24 UTC37
    client: matrix_sdk::Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC38
    handle: ClientHandle,
96fdf90aacd6atagen2026-09-08 08:24 UTC39
    events: EventStream,
fe4133dbcd47atagen2026-09-26 09:05 UTC40
    running: tokio::task::JoinHandle<Result<macltok::Termination, EngineError>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC41
    media_id: MediaId,
96fdf90aacd6atagen2026-09-08 08:24 UTC42
}
96fdf90aacd6atagen2026-09-08 08:24 UTC43
96fdf90aacd6atagen2026-09-08 08:24 UTC44
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC45
async fn full_size_uses_full_source() {
96fdf90aacd6atagen2026-09-08 08:24 UTC46
    let mut harness = start(file_with_thumbnail()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC47
    mount_downloads(&harness.server).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC48
31ce2453c82fatagen2026-09-24 13:07 UTC49
    let bytes = fetch(&harness, MediaSize::Full).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC50
    assert_eq!(&*bytes, b"binaryjpegfullimagedata");
96fdf90aacd6atagen2026-09-08 08:24 UTC51
    assert!(
96fdf90aacd6atagen2026-09-08 08:24 UTC52
        media_paths(&harness.server)
96fdf90aacd6atagen2026-09-08 08:24 UTC53
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC54
            .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC55
            .any(|path| path.ends_with("/localhost/full"))
96fdf90aacd6atagen2026-09-08 08:24 UTC56
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC57
31ce2453c82fatagen2026-09-24 13:07 UTC58
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC59
}
96fdf90aacd6atagen2026-09-08 08:24 UTC60
96fdf90aacd6atagen2026-09-08 08:24 UTC61
#[tokio::test]
fa9114fcf75fatagen2026-09-24 12:51 UTC62
async fn encrypted_media_is_cached_as_plaintext_like_the_sdk_does() {
fa9114fcf75fatagen2026-09-24 12:51 UTC63
    use std::io::Read as _;
fa9114fcf75fatagen2026-09-24 12:51 UTC64
fa9114fcf75fatagen2026-09-24 12:51 UTC65
    let plaintext = b"very secret image".to_vec();
fa9114fcf75fatagen2026-09-24 12:51 UTC66
    let mut cursor = std::io::Cursor::new(plaintext.clone());
fa9114fcf75fatagen2026-09-24 12:51 UTC67
    let mut encryptor = matrix_sdk_crypto::AttachmentEncryptor::new(&mut cursor);
fa9114fcf75fatagen2026-09-24 12:51 UTC68
    let mut ciphertext = Vec::new();
fa9114fcf75fatagen2026-09-24 12:51 UTC69
    encryptor.read_to_end(&mut ciphertext).unwrap();
fa9114fcf75fatagen2026-09-24 12:51 UTC70
    let keys = encryptor.finish();
fa9114fcf75fatagen2026-09-24 12:51 UTC71
    let file = EncryptedFile::new(
fa9114fcf75fatagen2026-09-24 12:51 UTC72
        mxc_uri!("mxc://localhost/secret").to_owned(),
fa9114fcf75fatagen2026-09-24 12:51 UTC73
        keys.encryption_info,
fa9114fcf75fatagen2026-09-24 12:51 UTC74
        keys.hashes,
fa9114fcf75fatagen2026-09-24 12:51 UTC75
    );
fa9114fcf75fatagen2026-09-24 12:51 UTC76
    let source = MediaSource::Encrypted(Box::new(file.clone()));
fa9114fcf75fatagen2026-09-24 12:51 UTC77
    let mut harness = start(RoomMessageEventContent::new(MessageType::Image(
fa9114fcf75fatagen2026-09-24 12:51 UTC78
        ImageMessageEventContent::encrypted("secret.png".to_owned(), file),
fa9114fcf75fatagen2026-09-24 12:51 UTC79
    )))
fa9114fcf75fatagen2026-09-24 12:51 UTC80
    .await;
fa9114fcf75fatagen2026-09-24 12:51 UTC81
    Mock::given(method("GET"))
fa9114fcf75fatagen2026-09-24 12:51 UTC82
        .and(path_regex("/download/localhost/secret$"))
fa9114fcf75fatagen2026-09-24 12:51 UTC83
        .respond_with(ResponseTemplate::new(200).set_body_bytes(ciphertext))
fa9114fcf75fatagen2026-09-24 12:51 UTC84
        .mount(harness.server.server())
fa9114fcf75fatagen2026-09-24 12:51 UTC85
        .await;
fa9114fcf75fatagen2026-09-24 12:51 UTC86
fa9114fcf75fatagen2026-09-24 12:51 UTC87
    let from_sdk = harness
fa9114fcf75fatagen2026-09-24 12:51 UTC88
        .client
fa9114fcf75fatagen2026-09-24 12:51 UTC89
        .media()
fa9114fcf75fatagen2026-09-24 12:51 UTC90
        .get_media_content(
fa9114fcf75fatagen2026-09-24 12:51 UTC91
            &MediaRequestParameters {
fa9114fcf75fatagen2026-09-24 12:51 UTC92
                source,
fa9114fcf75fatagen2026-09-24 12:51 UTC93
                format: MediaFormat::File,
fa9114fcf75fatagen2026-09-24 12:51 UTC94
            },
fa9114fcf75fatagen2026-09-24 12:51 UTC95
            true,
fa9114fcf75fatagen2026-09-24 12:51 UTC96
        )
fa9114fcf75fatagen2026-09-24 12:51 UTC97
        .await
fa9114fcf75fatagen2026-09-24 12:51 UTC98
        .unwrap();
fa9114fcf75fatagen2026-09-24 12:51 UTC99
    assert_eq!(from_sdk, plaintext);
31ce2453c82fatagen2026-09-24 13:07 UTC100
    assert_eq!(&*fetch(&harness, MediaSize::Full).await, &plaintext[..]);
fa9114fcf75fatagen2026-09-24 12:51 UTC101
    assert_eq!(media_paths(&harness.server).await.len(), 1);
fa9114fcf75fatagen2026-09-24 12:51 UTC102
31ce2453c82fatagen2026-09-24 13:07 UTC103
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
fa9114fcf75fatagen2026-09-24 12:51 UTC104
}
fa9114fcf75fatagen2026-09-24 12:51 UTC105
fa9114fcf75fatagen2026-09-24 12:51 UTC106
#[tokio::test]
5a52012c9b1datagen2026-09-24 23:45 UTC107
async fn a_plain_event_cannot_poison_an_encrypted_files_cache() {
5a52012c9b1datagen2026-09-24 23:45 UTC108
    use std::io::Read as _;
5a52012c9b1datagen2026-09-24 23:45 UTC109
5a52012c9b1datagen2026-09-24 23:45 UTC110
    let plaintext = b"very secret image".to_vec();
5a52012c9b1datagen2026-09-24 23:45 UTC111
    let mut cursor = std::io::Cursor::new(plaintext.clone());
5a52012c9b1datagen2026-09-24 23:45 UTC112
    let mut encryptor = matrix_sdk_crypto::AttachmentEncryptor::new(&mut cursor);
5a52012c9b1datagen2026-09-24 23:45 UTC113
    let mut ciphertext = Vec::new();
5a52012c9b1datagen2026-09-24 23:45 UTC114
    encryptor.read_to_end(&mut ciphertext).unwrap();
5a52012c9b1datagen2026-09-24 23:45 UTC115
    let keys = encryptor.finish();
5a52012c9b1datagen2026-09-24 23:45 UTC116
    let uri = mxc_uri!("mxc://localhost/secret").to_owned();
5a52012c9b1datagen2026-09-24 23:45 UTC117
    let file = EncryptedFile::new(uri.clone(), keys.encryption_info, keys.hashes);
5a52012c9b1datagen2026-09-24 23:45 UTC118
    let mut harness = start(RoomMessageEventContent::new(MessageType::Image(
5a52012c9b1datagen2026-09-24 23:45 UTC119
        ImageMessageEventContent::plain("bait.png".to_owned(), uri),
5a52012c9b1datagen2026-09-24 23:45 UTC120
    )))
5a52012c9b1datagen2026-09-24 23:45 UTC121
    .await;
5a52012c9b1datagen2026-09-24 23:45 UTC122
    Mock::given(method("GET"))
5a52012c9b1datagen2026-09-24 23:45 UTC123
        .and(path_regex("/download/localhost/secret$"))
5a52012c9b1datagen2026-09-24 23:45 UTC124
        .respond_with(ResponseTemplate::new(200).set_body_bytes(ciphertext.clone()))
5a52012c9b1datagen2026-09-24 23:45 UTC125
        .mount(harness.server.server())
5a52012c9b1datagen2026-09-24 23:45 UTC126
        .await;
5a52012c9b1datagen2026-09-24 23:45 UTC127
    assert_eq!(&*fetch(&harness, MediaSize::Full).await, &ciphertext[..]);
5a52012c9b1datagen2026-09-24 23:45 UTC128
5a52012c9b1datagen2026-09-24 23:45 UTC129
    harness
5a52012c9b1datagen2026-09-24 23:45 UTC130
        .server
5a52012c9b1datagen2026-09-24 23:45 UTC131
        .sync_room(
5a52012c9b1datagen2026-09-24 23:45 UTC132
            &harness.client,
5a52012c9b1datagen2026-09-24 23:45 UTC133
            JoinedRoomBuilder::new(room_id!("!media:localhost")).add_timeline_event(
5a52012c9b1datagen2026-09-24 23:45 UTC134
                EventFactory::new()
5a52012c9b1datagen2026-09-24 23:45 UTC135
                    .event(RoomMessageEventContent::new(MessageType::Image(
5a52012c9b1datagen2026-09-24 23:45 UTC136
                        ImageMessageEventContent::encrypted("secret.png".to_owned(), file),
5a52012c9b1datagen2026-09-24 23:45 UTC137
                    )))
5a52012c9b1datagen2026-09-24 23:45 UTC138
                    .sender(&ALICE),
5a52012c9b1datagen2026-09-24 23:45 UTC139
            ),
5a52012c9b1datagen2026-09-24 23:45 UTC140
        )
5a52012c9b1datagen2026-09-24 23:45 UTC141
        .await;
5a52012c9b1datagen2026-09-24 23:45 UTC142
    let event = support::receive_until(&mut harness.events, |event| {
5a52012c9b1datagen2026-09-24 23:45 UTC143
        support::event_messages(event).into_iter().any(|message| {
5a52012c9b1datagen2026-09-24 23:45 UTC144
            support::media_of(message).is_some_and(|media| media.filename == "secret.png")
5a52012c9b1datagen2026-09-24 23:45 UTC145
        })
5a52012c9b1datagen2026-09-24 23:45 UTC146
    })
5a52012c9b1datagen2026-09-24 23:45 UTC147
    .await;
5a52012c9b1datagen2026-09-24 23:45 UTC148
    let media_id = support::event_messages(&event)
5a52012c9b1datagen2026-09-24 23:45 UTC149
        .into_iter()
5a52012c9b1datagen2026-09-24 23:45 UTC150
        .find_map(|message| {
5a52012c9b1datagen2026-09-24 23:45 UTC151
            support::media_of(message).filter(|media| media.filename == "secret.png")
5a52012c9b1datagen2026-09-24 23:45 UTC152
        })
5a52012c9b1datagen2026-09-24 23:45 UTC153
        .map(|media| media.id.clone())
5a52012c9b1datagen2026-09-24 23:45 UTC154
        .unwrap();
5a52012c9b1datagen2026-09-24 23:45 UTC155
    let media = harness
5a52012c9b1datagen2026-09-24 23:45 UTC156
        .handle
5a52012c9b1datagen2026-09-24 23:45 UTC157
        .media(&media_id, MediaSize::Full)
5a52012c9b1datagen2026-09-24 23:45 UTC158
        .await
5a52012c9b1datagen2026-09-24 23:45 UTC159
        .unwrap();
5a52012c9b1datagen2026-09-24 23:45 UTC160
    assert_eq!(&*media.bytes, &plaintext[..]);
5a52012c9b1datagen2026-09-24 23:45 UTC161
5a52012c9b1datagen2026-09-24 23:45 UTC162
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
5a52012c9b1datagen2026-09-24 23:45 UTC163
}
5a52012c9b1datagen2026-09-24 23:45 UTC164
5a52012c9b1datagen2026-09-24 23:45 UTC165
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC166
async fn simultaneous_fetches_share_one_download() {
96fdf90aacd6atagen2026-09-08 08:24 UTC167
    let mut harness = start(plain_image()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC168
    Mock::given(method("GET"))
96fdf90aacd6atagen2026-09-08 08:24 UTC169
        .and(path_regex("/download/localhost/image$"))
96fdf90aacd6atagen2026-09-08 08:24 UTC170
        .respond_with(
96fdf90aacd6atagen2026-09-08 08:24 UTC171
            ResponseTemplate::new(200)
96fdf90aacd6atagen2026-09-08 08:24 UTC172
                .set_body_bytes(b"shared image bytes".to_vec())
96fdf90aacd6atagen2026-09-08 08:24 UTC173
                .set_delay(Duration::from_millis(300)),
96fdf90aacd6atagen2026-09-08 08:24 UTC174
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC175
        .mount(harness.server.server())
96fdf90aacd6atagen2026-09-08 08:24 UTC176
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC177
31ce2453c82fatagen2026-09-24 13:07 UTC178
    let (first, second) = tokio::time::timeout(Duration::from_secs(5), async {
31ce2453c82fatagen2026-09-24 13:07 UTC179
        tokio::join!(
31ce2453c82fatagen2026-09-24 13:07 UTC180
            harness.handle.media(&harness.media_id, MediaSize::Full),
31ce2453c82fatagen2026-09-24 13:07 UTC181
            harness.handle.media(&harness.media_id, MediaSize::Full),
31ce2453c82fatagen2026-09-24 13:07 UTC182
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC183
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC184
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC185
    .expect("both fetches did not complete");
31ce2453c82fatagen2026-09-24 13:07 UTC186
    assert_eq!(&*first.unwrap().bytes, b"shared image bytes");
31ce2453c82fatagen2026-09-24 13:07 UTC187
    assert_eq!(&*second.unwrap().bytes, b"shared image bytes");
31ce2453c82fatagen2026-09-24 13:07 UTC188
96fdf90aacd6atagen2026-09-08 08:24 UTC189
    let downloads = harness
96fdf90aacd6atagen2026-09-08 08:24 UTC190
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC191
        .received_requests()
96fdf90aacd6atagen2026-09-08 08:24 UTC192
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC193
        .unwrap()
96fdf90aacd6atagen2026-09-08 08:24 UTC194
        .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC195
        .filter(|request| request.url.path().ends_with("/download/localhost/image"))
96fdf90aacd6atagen2026-09-08 08:24 UTC196
        .count();
96fdf90aacd6atagen2026-09-08 08:24 UTC197
    assert_eq!(downloads, 1, "duplicate in-flight media download");
31ce2453c82fatagen2026-09-24 13:07 UTC198
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC199
}
96fdf90aacd6atagen2026-09-08 08:24 UTC200
96fdf90aacd6atagen2026-09-08 08:24 UTC201
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC202
async fn downloaded_media_over_limit_is_rejected() {
96fdf90aacd6atagen2026-09-08 08:24 UTC203
    let mut harness = start_with_config(
96fdf90aacd6atagen2026-09-08 08:24 UTC204
        plain_image(),
96fdf90aacd6atagen2026-09-08 08:24 UTC205
        EngineConfig::default()
96fdf90aacd6atagen2026-09-08 08:24 UTC206
            .with_initial_history_limit(0)
96fdf90aacd6atagen2026-09-08 08:24 UTC207
            .with_max_media_bytes(4),
96fdf90aacd6atagen2026-09-08 08:24 UTC208
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC209
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC210
    Mock::given(method("GET"))
96fdf90aacd6atagen2026-09-08 08:24 UTC211
        .and(path_regex("/download/localhost/image$"))
96fdf90aacd6atagen2026-09-08 08:24 UTC212
        .respond_with(ResponseTemplate::new(200).set_body_bytes(b"too large".to_vec()))
96fdf90aacd6atagen2026-09-08 08:24 UTC213
        .mount(harness.server.server())
96fdf90aacd6atagen2026-09-08 08:24 UTC214
        .await;
31ce2453c82fatagen2026-09-24 13:07 UTC215
31ce2453c82fatagen2026-09-24 13:07 UTC216
    let error = harness
96fdf90aacd6atagen2026-09-08 08:24 UTC217
        .handle
31ce2453c82fatagen2026-09-24 13:07 UTC218
        .media(&harness.media_id, MediaSize::Full)
96fdf90aacd6atagen2026-09-08 08:24 UTC219
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC220
        .unwrap_err();
31ce2453c82fatagen2026-09-24 13:07 UTC221
    assert_eq!(error.kind, ErrorKind::MediaTooLarge);
31ce2453c82fatagen2026-09-24 13:07 UTC222
31ce2453c82fatagen2026-09-24 13:07 UTC223
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC224
}
96fdf90aacd6atagen2026-09-08 08:24 UTC225
96fdf90aacd6atagen2026-09-08 08:24 UTC226
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC227
async fn thumbnail_size_prefers_explicit_thumbnail() {
96fdf90aacd6atagen2026-09-08 08:24 UTC228
    let mut harness = start(file_with_thumbnail()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC229
    mount_downloads(&harness.server).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC230
31ce2453c82fatagen2026-09-24 13:07 UTC231
    let bytes = fetch(&harness, thumbnail_size()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC232
    assert_eq!(&*bytes, b"binaryjpegfullimagedata");
96fdf90aacd6atagen2026-09-08 08:24 UTC233
    let paths = media_paths(&harness.server).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC234
    assert!(
96fdf90aacd6atagen2026-09-08 08:24 UTC235
        paths
96fdf90aacd6atagen2026-09-08 08:24 UTC236
            .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC237
            .any(|path| path.ends_with("/localhost/explicit-thumbnail"))
96fdf90aacd6atagen2026-09-08 08:24 UTC238
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC239
    assert!(!paths.iter().any(|path| path.ends_with("/localhost/full")));
96fdf90aacd6atagen2026-09-08 08:24 UTC240
31ce2453c82fatagen2026-09-24 13:07 UTC241
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC242
}
96fdf90aacd6atagen2026-09-08 08:24 UTC243
96fdf90aacd6atagen2026-09-08 08:24 UTC244
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC245
async fn plain_image_thumbnail_uses_thumbnail_endpoint() {
96fdf90aacd6atagen2026-09-08 08:24 UTC246
    let mut harness = start(plain_image()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC247
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC248
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC249
        .mock_media_thumbnail(Method::Scale, 800, 800, false)
96fdf90aacd6atagen2026-09-08 08:24 UTC250
        .ok()
96fdf90aacd6atagen2026-09-08 08:24 UTC251
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC252
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC253
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC254
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC255
        .mock_authed_media_thumbnail(Method::Scale, 800, 800, false)
96fdf90aacd6atagen2026-09-08 08:24 UTC256
        .ok()
96fdf90aacd6atagen2026-09-08 08:24 UTC257
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC258
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC259
31ce2453c82fatagen2026-09-24 13:07 UTC260
    let bytes = fetch(&harness, thumbnail_size()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC261
    assert_eq!(&*bytes, b"binaryjpegthumbnaildata");
96fdf90aacd6atagen2026-09-08 08:24 UTC262
    assert!(
96fdf90aacd6atagen2026-09-08 08:24 UTC263
        media_paths(&harness.server)
96fdf90aacd6atagen2026-09-08 08:24 UTC264
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC265
            .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC266
            .any(|path| path.contains("/thumbnail/localhost/image"))
96fdf90aacd6atagen2026-09-08 08:24 UTC267
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC268
31ce2453c82fatagen2026-09-24 13:07 UTC269
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC270
}
96fdf90aacd6atagen2026-09-08 08:24 UTC271
96fdf90aacd6atagen2026-09-08 08:24 UTC272
#[tokio::test]
31ce2453c82fatagen2026-09-24 13:07 UTC273
async fn audio_thumbnail_fetch_fails_with_media_unavailable() {
96fdf90aacd6atagen2026-09-08 08:24 UTC274
    let mut harness = start(plain_audio()).await;
31ce2453c82fatagen2026-09-24 13:07 UTC275
31ce2453c82fatagen2026-09-24 13:07 UTC276
    let error = harness
96fdf90aacd6atagen2026-09-08 08:24 UTC277
        .handle
31ce2453c82fatagen2026-09-24 13:07 UTC278
        .media(&harness.media_id, thumbnail_size())
96fdf90aacd6atagen2026-09-08 08:24 UTC279
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC280
        .unwrap_err();
31ce2453c82fatagen2026-09-24 13:07 UTC281
    assert_eq!(error.kind, ErrorKind::MediaUnavailable);
96fdf90aacd6atagen2026-09-08 08:24 UTC282
31ce2453c82fatagen2026-09-24 13:07 UTC283
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC284
}
96fdf90aacd6atagen2026-09-08 08:24 UTC285
96fdf90aacd6atagen2026-09-08 08:24 UTC286
#[tokio::test]
5b21b919d1e6atagen2026-09-27 11:47 UTC287
async fn media_source_locates_what_media_would_fetch() {
5b21b919d1e6atagen2026-09-27 11:47 UTC288
    let keys =
5b21b919d1e6atagen2026-09-27 11:47 UTC289
        matrix_sdk_crypto::AttachmentEncryptor::new(&mut std::io::Cursor::new(b"secret")).finish();
5b21b919d1e6atagen2026-09-27 11:47 UTC290
    let file = EncryptedFile::new(
5b21b919d1e6atagen2026-09-27 11:47 UTC291
        mxc_uri!("mxc://localhost/secret").to_owned(),
5b21b919d1e6atagen2026-09-27 11:47 UTC292
        keys.encryption_info,
5b21b919d1e6atagen2026-09-27 11:47 UTC293
        keys.hashes,
5b21b919d1e6atagen2026-09-27 11:47 UTC294
    );
5b21b919d1e6atagen2026-09-27 11:47 UTC295
    let wire = serde_json::to_value(&file).unwrap();
5b21b919d1e6atagen2026-09-27 11:47 UTC296
    let mut harness = start(RoomMessageEventContent::new(MessageType::File(
5b21b919d1e6atagen2026-09-27 11:47 UTC297
        FileMessageEventContent::encrypted("secret.bin".to_owned(), file),
5b21b919d1e6atagen2026-09-27 11:47 UTC298
    )))
5b21b919d1e6atagen2026-09-27 11:47 UTC299
    .await;
5b21b919d1e6atagen2026-09-27 11:47 UTC300
5b21b919d1e6atagen2026-09-27 11:47 UTC301
    let MediaLocation::File(MediaFile {
5b21b919d1e6atagen2026-09-27 11:47 UTC302
        url,
5b21b919d1e6atagen2026-09-27 11:47 UTC303
        encryption: Some(encryption),
5b21b919d1e6atagen2026-09-27 11:47 UTC304
        ..
5b21b919d1e6atagen2026-09-27 11:47 UTC305
    }) = harness
5b21b919d1e6atagen2026-09-27 11:47 UTC306
        .handle
5b21b919d1e6atagen2026-09-27 11:47 UTC307
        .media_source(&harness.media_id, MediaSize::Full)
5b21b919d1e6atagen2026-09-27 11:47 UTC308
        .await
5b21b919d1e6atagen2026-09-27 11:47 UTC309
        .unwrap()
5b21b919d1e6atagen2026-09-27 11:47 UTC310
    else {
5b21b919d1e6atagen2026-09-27 11:47 UTC311
        panic!("an encrypted file should locate as one");
5b21b919d1e6atagen2026-09-27 11:47 UTC312
    };
5b21b919d1e6atagen2026-09-27 11:47 UTC313
    assert_eq!(
5b21b919d1e6atagen2026-09-27 11:47 UTC314
        url,
5b21b919d1e6atagen2026-09-27 11:47 UTC315
        format!(
5b21b919d1e6atagen2026-09-27 11:47 UTC316
            "{}/_matrix/client/v1/media/download/localhost/secret",
5b21b919d1e6atagen2026-09-27 11:47 UTC317
            harness.server.uri()
5b21b919d1e6atagen2026-09-27 11:47 UTC318
        )
5b21b919d1e6atagen2026-09-27 11:47 UTC319
    );
5b21b919d1e6atagen2026-09-27 11:47 UTC320
    assert_eq!(encryption.key, wire["key"]["k"]);
5b21b919d1e6atagen2026-09-27 11:47 UTC321
    assert_eq!(encryption.iv, wire["iv"]);
5b21b919d1e6atagen2026-09-27 11:47 UTC322
    assert_eq!(encryption.sha256, wire["hashes"]["sha256"]);
5b21b919d1e6atagen2026-09-27 11:47 UTC323
5b21b919d1e6atagen2026-09-27 11:47 UTC324
    let video = sync_media(
5b21b919d1e6atagen2026-09-27 11:47 UTC325
        &mut harness,
5b21b919d1e6atagen2026-09-27 11:47 UTC326
        "video.mp4",
5b21b919d1e6atagen2026-09-27 11:47 UTC327
        RoomMessageEventContent::new(MessageType::Video(VideoMessageEventContent::plain(
5b21b919d1e6atagen2026-09-27 11:47 UTC328
            "video.mp4".to_owned(),
5b21b919d1e6atagen2026-09-27 11:47 UTC329
            mxc_uri!("mxc://localhost/video").to_owned(),
5b21b919d1e6atagen2026-09-27 11:47 UTC330
        ))),
5b21b919d1e6atagen2026-09-27 11:47 UTC331
    )
5b21b919d1e6atagen2026-09-27 11:47 UTC332
    .await;
5b21b919d1e6atagen2026-09-27 11:47 UTC333
    let MediaLocation::ServerThumbnail { url, fallback } = harness
5b21b919d1e6atagen2026-09-27 11:47 UTC334
        .handle
5b21b919d1e6atagen2026-09-27 11:47 UTC335
        .media_source(&video, thumbnail_size())
5b21b919d1e6atagen2026-09-27 11:47 UTC336
        .await
5b21b919d1e6atagen2026-09-27 11:47 UTC337
        .unwrap()
5b21b919d1e6atagen2026-09-27 11:47 UTC338
    else {
5b21b919d1e6atagen2026-09-27 11:47 UTC339
        panic!("a video with no thumbnail of its own should ask the server for one");
5b21b919d1e6atagen2026-09-27 11:47 UTC340
    };
5b21b919d1e6atagen2026-09-27 11:47 UTC341
    let url = matrix_sdk::reqwest::Url::parse(&url).unwrap();
5b21b919d1e6atagen2026-09-27 11:47 UTC342
    assert_eq!(
5b21b919d1e6atagen2026-09-27 11:47 UTC343
        url.path(),
5b21b919d1e6atagen2026-09-27 11:47 UTC344
        "/_matrix/client/v1/media/thumbnail/localhost/video"
5b21b919d1e6atagen2026-09-27 11:47 UTC345
    );
5b21b919d1e6atagen2026-09-27 11:47 UTC346
    let query: Vec<_> = url.query_pairs().into_owned().collect();
5b21b919d1e6atagen2026-09-27 11:47 UTC347
    for pair in [("width", "800"), ("height", "800"), ("method", "scale")] {
5b21b919d1e6atagen2026-09-27 11:47 UTC348
        assert!(query.contains(&(pair.0.to_owned(), pair.1.to_owned())));
5b21b919d1e6atagen2026-09-27 11:47 UTC349
    }
5b21b919d1e6atagen2026-09-27 11:47 UTC350
    assert_eq!(
5b21b919d1e6atagen2026-09-27 11:47 UTC351
        MediaLocation::File(fallback),
5b21b919d1e6atagen2026-09-27 11:47 UTC352
        harness
5b21b919d1e6atagen2026-09-27 11:47 UTC353
            .handle
5b21b919d1e6atagen2026-09-27 11:47 UTC354
            .media_source(&video, MediaSize::Full)
5b21b919d1e6atagen2026-09-27 11:47 UTC355
            .await
5b21b919d1e6atagen2026-09-27 11:47 UTC356
            .unwrap()
5b21b919d1e6atagen2026-09-27 11:47 UTC357
    );
5b21b919d1e6atagen2026-09-27 11:47 UTC358
5b21b919d1e6atagen2026-09-27 11:47 UTC359
    let error = harness
5b21b919d1e6atagen2026-09-27 11:47 UTC360
        .handle
5b21b919d1e6atagen2026-09-27 11:47 UTC361
        .media_source(&MediaId::from("made up".to_owned()), MediaSize::Full)
5b21b919d1e6atagen2026-09-27 11:47 UTC362
        .await
5b21b919d1e6atagen2026-09-27 11:47 UTC363
        .unwrap_err();
5b21b919d1e6atagen2026-09-27 11:47 UTC364
    assert_eq!(error.kind, ErrorKind::MediaUnknown);
5b21b919d1e6atagen2026-09-27 11:47 UTC365
5b21b919d1e6atagen2026-09-27 11:47 UTC366
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
5b21b919d1e6atagen2026-09-27 11:47 UTC367
}
5b21b919d1e6atagen2026-09-27 11:47 UTC368
5b21b919d1e6atagen2026-09-27 11:47 UTC369
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC370
async fn media_upload_is_queued_and_sent() {
96fdf90aacd6atagen2026-09-08 08:24 UTC371
    let mut harness = start(plain_image()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC372
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC373
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC374
        .mock_authenticated_media_config()
96fdf90aacd6atagen2026-09-08 08:24 UTC375
        .ok_default()
96fdf90aacd6atagen2026-09-08 08:24 UTC376
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC377
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC378
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC379
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC380
        .mock_upload()
96fdf90aacd6atagen2026-09-08 08:24 UTC381
        .expect_mime_type("image/jpeg")
96fdf90aacd6atagen2026-09-08 08:24 UTC382
        .ok(mxc_uri!("mxc://localhost/uploaded"))
96fdf90aacd6atagen2026-09-08 08:24 UTC383
        .expect(1)
96fdf90aacd6atagen2026-09-08 08:24 UTC384
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC385
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC386
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC387
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC388
        .mock_room_send()
96fdf90aacd6atagen2026-09-08 08:24 UTC389
        .ok(event_id!("$uploaded:localhost"))
96fdf90aacd6atagen2026-09-08 08:24 UTC390
        .expect(1)
96fdf90aacd6atagen2026-09-08 08:24 UTC391
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC392
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC393
31ce2453c82fatagen2026-09-24 13:07 UTC394
    let mut upload = AttachmentUpload::new(
31ce2453c82fatagen2026-09-24 13:07 UTC395
        room_id!("!media:localhost").to_owned(),
31ce2453c82fatagen2026-09-24 13:07 UTC396
        "photo.jpg",
31ce2453c82fatagen2026-09-24 13:07 UTC397
        "image/jpeg",
31ce2453c82fatagen2026-09-24 13:07 UTC398
        b"photo data".to_vec(),
31ce2453c82fatagen2026-09-24 13:07 UTC399
    );
31ce2453c82fatagen2026-09-24 13:07 UTC400
    upload.caption = Some("holiday".to_owned());
31ce2453c82fatagen2026-09-24 13:07 UTC401
    harness.handle.upload(upload).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC402
96fdf90aacd6atagen2026-09-08 08:24 UTC403
    tokio::time::timeout(Duration::from_secs(5), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC404
        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC405
            let requests = harness.server.received_requests().await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC406
            if requests.iter().any(|request| {
96fdf90aacd6atagen2026-09-08 08:24 UTC407
                request.url.path().contains("/send/m.room.message/")
96fdf90aacd6atagen2026-09-08 08:24 UTC408
                    && String::from_utf8_lossy(&request.body).contains("mxc://localhost/uploaded")
96fdf90aacd6atagen2026-09-08 08:24 UTC409
            }) {
96fdf90aacd6atagen2026-09-08 08:24 UTC410
                break;
96fdf90aacd6atagen2026-09-08 08:24 UTC411
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC412
            tokio::task::yield_now().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC413
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC414
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC415
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC416
    .expect("queued media was not sent");
96fdf90aacd6atagen2026-09-08 08:24 UTC417
31ce2453c82fatagen2026-09-24 13:07 UTC418
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC419
}
96fdf90aacd6atagen2026-09-08 08:24 UTC420
96fdf90aacd6atagen2026-09-08 08:24 UTC421
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC422
async fn queued_offline_attachment_exposes_fetchable_local_media() {
96fdf90aacd6atagen2026-09-08 08:24 UTC423
    let mut harness = start(plain_image()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC424
    harness.client.send_queue().set_enabled(false).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC425
    let original = b"offline image bytes".to_vec();
96fdf90aacd6atagen2026-09-08 08:24 UTC426
    let mut upload = AttachmentUpload::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC427
        room_id!("!media:localhost").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC428
        "pending.png",
96fdf90aacd6atagen2026-09-08 08:24 UTC429
        "image/png",
96fdf90aacd6atagen2026-09-08 08:24 UTC430
        original.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC431
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC432
    upload.details = Some(UploadDetails::Image {
96fdf90aacd6atagen2026-09-08 08:24 UTC433
        width: Some(32),
96fdf90aacd6atagen2026-09-08 08:24 UTC434
        height: Some(16),
96fdf90aacd6atagen2026-09-08 08:24 UTC435
        blurhash: None,
96fdf90aacd6atagen2026-09-08 08:24 UTC436
    });
31ce2453c82fatagen2026-09-24 13:07 UTC437
    harness.handle.upload(upload).await.unwrap();
31ce2453c82fatagen2026-09-24 13:07 UTC438
    let event = support::receive_until(&mut harness.events, |event| {
960ac0721079atagen2026-09-25 00:24 UTC439
        support::event_messages(event).into_iter().any(|event| {
960ac0721079atagen2026-09-25 00:24 UTC440
            event.own
960ac0721079atagen2026-09-25 00:24 UTC441
                && event.transaction_id.is_some()
960ac0721079atagen2026-09-25 00:24 UTC442
                && support::media_of(event).is_some_and(|media| media.filename == "pending.png")
96fdf90aacd6atagen2026-09-08 08:24 UTC443
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC444
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC445
    .await;
31ce2453c82fatagen2026-09-24 13:07 UTC446
    let media = support::event_messages(&event)
31ce2453c82fatagen2026-09-24 13:07 UTC447
        .into_iter()
960ac0721079atagen2026-09-25 00:24 UTC448
        .find(|event| event.own && support::media_of(event).is_some())
960ac0721079atagen2026-09-25 00:24 UTC449
        .and_then(support::media_of)
96fdf90aacd6atagen2026-09-08 08:24 UTC450
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC451
    assert_eq!((media.width, media.height), (Some(32), Some(16)));
96fdf90aacd6atagen2026-09-08 08:24 UTC452
    assert_eq!(media.size_bytes, Some(original.len() as u64));
96fdf90aacd6atagen2026-09-08 08:24 UTC453
    let media_id = media.id.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC454
31ce2453c82fatagen2026-09-24 13:07 UTC455
    let fetched = harness
96fdf90aacd6atagen2026-09-08 08:24 UTC456
        .handle
31ce2453c82fatagen2026-09-24 13:07 UTC457
        .media(&media_id, MediaSize::Full)
96fdf90aacd6atagen2026-09-08 08:24 UTC458
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC459
        .unwrap();
31ce2453c82fatagen2026-09-24 13:07 UTC460
    assert_eq!(&*fetched.bytes, &original[..]);
5b21b919d1e6atagen2026-09-27 11:47 UTC461
    assert_eq!(
5b21b919d1e6atagen2026-09-27 11:47 UTC462
        harness
5b21b919d1e6atagen2026-09-27 11:47 UTC463
            .handle
5b21b919d1e6atagen2026-09-27 11:47 UTC464
            .media_source(&media_id, MediaSize::Full)
5b21b919d1e6atagen2026-09-27 11:47 UTC465
            .await
5b21b919d1e6atagen2026-09-27 11:47 UTC466
            .unwrap(),
5b21b919d1e6atagen2026-09-27 11:47 UTC467
        MediaLocation::Local
5b21b919d1e6atagen2026-09-27 11:47 UTC468
    );
31ce2453c82fatagen2026-09-24 13:07 UTC469
31ce2453c82fatagen2026-09-24 13:07 UTC470
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC471
}
96fdf90aacd6atagen2026-09-08 08:24 UTC472
96fdf90aacd6atagen2026-09-08 08:24 UTC473
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC474
async fn image_upload_uses_caller_supplied_metadata_and_thumbnail() {
96fdf90aacd6atagen2026-09-08 08:24 UTC475
    let mut harness = start(plain_image()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC476
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC477
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC478
        .mock_authenticated_media_config()
96fdf90aacd6atagen2026-09-08 08:24 UTC479
        .ok_default()
96fdf90aacd6atagen2026-09-08 08:24 UTC480
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC481
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC482
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC483
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC484
        .mock_upload()
96fdf90aacd6atagen2026-09-08 08:24 UTC485
        .expect_mime_type("image/png")
96fdf90aacd6atagen2026-09-08 08:24 UTC486
        .respond_with(|request: &wiremock::Request| {
96fdf90aacd6atagen2026-09-08 08:24 UTC487
            let uri = if request.body == b"thumbnail bytes" {
96fdf90aacd6atagen2026-09-08 08:24 UTC488
                "mxc://localhost/image-thumb"
96fdf90aacd6atagen2026-09-08 08:24 UTC489
            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC490
                "mxc://localhost/image-upload"
96fdf90aacd6atagen2026-09-08 08:24 UTC491
            };
96fdf90aacd6atagen2026-09-08 08:24 UTC492
            ResponseTemplate::new(200).set_body_json(json!({ "content_uri": uri }))
96fdf90aacd6atagen2026-09-08 08:24 UTC493
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC494
        .expect(2)
96fdf90aacd6atagen2026-09-08 08:24 UTC495
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC496
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC497
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC498
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC499
        .mock_room_send()
96fdf90aacd6atagen2026-09-08 08:24 UTC500
        .ok(event_id!("$image-upload:localhost"))
96fdf90aacd6atagen2026-09-08 08:24 UTC501
        .expect(1)
96fdf90aacd6atagen2026-09-08 08:24 UTC502
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC503
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC504
96fdf90aacd6atagen2026-09-08 08:24 UTC505
    let original = b"image bytes".to_vec();
96fdf90aacd6atagen2026-09-08 08:24 UTC506
    let thumbnail_bytes = b"thumbnail bytes".to_vec();
96fdf90aacd6atagen2026-09-08 08:24 UTC507
    let mut upload = AttachmentUpload::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC508
        room_id!("!media:localhost").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC509
        "large.png",
96fdf90aacd6atagen2026-09-08 08:24 UTC510
        "image/png",
96fdf90aacd6atagen2026-09-08 08:24 UTC511
        original.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC512
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC513
    upload.details = Some(UploadDetails::Image {
96fdf90aacd6atagen2026-09-08 08:24 UTC514
        width: Some(900),
96fdf90aacd6atagen2026-09-08 08:24 UTC515
        height: Some(450),
96fdf90aacd6atagen2026-09-08 08:24 UTC516
        blurhash: None,
96fdf90aacd6atagen2026-09-08 08:24 UTC517
    });
31ce2453c82fatagen2026-09-24 13:07 UTC518
    upload.thumbnail = Some(UploadThumbnail::new(
31ce2453c82fatagen2026-09-24 13:07 UTC519
        thumbnail_bytes.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC520
        "image/png",
31ce2453c82fatagen2026-09-24 13:07 UTC521
        800,
31ce2453c82fatagen2026-09-24 13:07 UTC522
        400,
31ce2453c82fatagen2026-09-24 13:07 UTC523
    ));
31ce2453c82fatagen2026-09-24 13:07 UTC524
    harness.handle.upload(upload).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC525
96fdf90aacd6atagen2026-09-08 08:24 UTC526
    let content = uploaded_content(&harness.server).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC527
    assert_eq!(content["msgtype"], "m.image");
96fdf90aacd6atagen2026-09-08 08:24 UTC528
    assert_eq!(content["info"]["w"], 900);
96fdf90aacd6atagen2026-09-08 08:24 UTC529
    assert_eq!(content["info"]["h"], 450);
96fdf90aacd6atagen2026-09-08 08:24 UTC530
    assert_eq!(content["info"]["size"], original.len());
96fdf90aacd6atagen2026-09-08 08:24 UTC531
    assert_eq!(content["info"]["thumbnail_info"]["w"], 800);
96fdf90aacd6atagen2026-09-08 08:24 UTC532
    assert_eq!(content["info"]["thumbnail_info"]["h"], 400);
96fdf90aacd6atagen2026-09-08 08:24 UTC533
    assert_eq!(content["info"]["thumbnail_info"]["mimetype"], "image/png");
96fdf90aacd6atagen2026-09-08 08:24 UTC534
    assert_eq!(
96fdf90aacd6atagen2026-09-08 08:24 UTC535
        content["info"]["thumbnail_info"]["size"],
96fdf90aacd6atagen2026-09-08 08:24 UTC536
        thumbnail_bytes.len()
96fdf90aacd6atagen2026-09-08 08:24 UTC537
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC538
    assert_eq!(
96fdf90aacd6atagen2026-09-08 08:24 UTC539
        content["info"]["thumbnail_url"],
96fdf90aacd6atagen2026-09-08 08:24 UTC540
        "mxc://localhost/image-thumb"
96fdf90aacd6atagen2026-09-08 08:24 UTC541
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC542
    assert_eq!(content["url"], "mxc://localhost/image-upload");
96fdf90aacd6atagen2026-09-08 08:24 UTC543
96fdf90aacd6atagen2026-09-08 08:24 UTC544
    let requests = harness.server.received_requests().await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC545
    let uploads = requests
96fdf90aacd6atagen2026-09-08 08:24 UTC546
        .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC547
        .filter(|request| request.url.path().ends_with("/_matrix/media/v3/upload"))
96fdf90aacd6atagen2026-09-08 08:24 UTC548
        .collect::<Vec<_>>();
96fdf90aacd6atagen2026-09-08 08:24 UTC549
    assert_eq!(uploads.len(), 2);
96fdf90aacd6atagen2026-09-08 08:24 UTC550
    assert!(uploads.iter().any(|request| request.body == original));
96fdf90aacd6atagen2026-09-08 08:24 UTC551
    assert!(
96fdf90aacd6atagen2026-09-08 08:24 UTC552
        uploads
96fdf90aacd6atagen2026-09-08 08:24 UTC553
            .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC554
            .any(|request| request.body == thumbnail_bytes)
96fdf90aacd6atagen2026-09-08 08:24 UTC555
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC556
31ce2453c82fatagen2026-09-24 13:07 UTC557
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC558
}
96fdf90aacd6atagen2026-09-08 08:24 UTC559
96fdf90aacd6atagen2026-09-08 08:24 UTC560
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC561
async fn video_upload_preserves_metadata_and_supplied_thumbnail() {
96fdf90aacd6atagen2026-09-08 08:24 UTC562
    let mut harness = start(plain_image()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC563
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC564
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC565
        .mock_authenticated_media_config()
96fdf90aacd6atagen2026-09-08 08:24 UTC566
        .ok_default()
96fdf90aacd6atagen2026-09-08 08:24 UTC567
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC568
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC569
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC570
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC571
        .mock_upload()
96fdf90aacd6atagen2026-09-08 08:24 UTC572
        .expect_mime_type("image/png")
96fdf90aacd6atagen2026-09-08 08:24 UTC573
        .ok(mxc_uri!("mxc://localhost/video-thumb"))
96fdf90aacd6atagen2026-09-08 08:24 UTC574
        .expect(1)
96fdf90aacd6atagen2026-09-08 08:24 UTC575
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC576
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC577
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC578
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC579
        .mock_upload()
96fdf90aacd6atagen2026-09-08 08:24 UTC580
        .expect_mime_type("video/mp4")
96fdf90aacd6atagen2026-09-08 08:24 UTC581
        .ok(mxc_uri!("mxc://localhost/video-upload"))
96fdf90aacd6atagen2026-09-08 08:24 UTC582
        .expect(1)
96fdf90aacd6atagen2026-09-08 08:24 UTC583
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC584
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC585
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC586
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC587
        .mock_room_send()
96fdf90aacd6atagen2026-09-08 08:24 UTC588
        .ok(event_id!("$video-upload:localhost"))
96fdf90aacd6atagen2026-09-08 08:24 UTC589
        .expect(1)
96fdf90aacd6atagen2026-09-08 08:24 UTC590
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC591
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC592
96fdf90aacd6atagen2026-09-08 08:24 UTC593
    let mut upload = AttachmentUpload::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC594
        room_id!("!media:localhost").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC595
        "clip.mp4",
96fdf90aacd6atagen2026-09-08 08:24 UTC596
        "video/mp4",
96fdf90aacd6atagen2026-09-08 08:24 UTC597
        b"video bytes".to_vec(),
96fdf90aacd6atagen2026-09-08 08:24 UTC598
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC599
    upload.details = Some(UploadDetails::Video {
96fdf90aacd6atagen2026-09-08 08:24 UTC600
        width: Some(1920),
96fdf90aacd6atagen2026-09-08 08:24 UTC601
        height: Some(1080),
96fdf90aacd6atagen2026-09-08 08:24 UTC602
        duration_ms: Some(3500),
96fdf90aacd6atagen2026-09-08 08:24 UTC603
        blurhash: Some("blurhash".to_owned()),
96fdf90aacd6atagen2026-09-08 08:24 UTC604
    });
31ce2453c82fatagen2026-09-24 13:07 UTC605
    upload.thumbnail = Some(UploadThumbnail::new(
31ce2453c82fatagen2026-09-24 13:07 UTC606
        b"poster bytes".to_vec(),
31ce2453c82fatagen2026-09-24 13:07 UTC607
        "image/png",
31ce2453c82fatagen2026-09-24 13:07 UTC608
        320,
31ce2453c82fatagen2026-09-24 13:07 UTC609
        180,
31ce2453c82fatagen2026-09-24 13:07 UTC610
    ));
31ce2453c82fatagen2026-09-24 13:07 UTC611
    harness.handle.upload(upload).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC612
96fdf90aacd6atagen2026-09-08 08:24 UTC613
    let content = uploaded_content(&harness.server).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC614
    assert_eq!(content["msgtype"], "m.video");
96fdf90aacd6atagen2026-09-08 08:24 UTC615
    assert_eq!(content["info"]["w"], 1920);
96fdf90aacd6atagen2026-09-08 08:24 UTC616
    assert_eq!(content["info"]["h"], 1080);
96fdf90aacd6atagen2026-09-08 08:24 UTC617
    assert_eq!(content["info"]["duration"], 3500);
96fdf90aacd6atagen2026-09-08 08:24 UTC618
    assert_eq!(content["info"]["size"], 11);
96fdf90aacd6atagen2026-09-08 08:24 UTC619
    assert_eq!(content["info"]["thumbnail_info"]["w"], 320);
96fdf90aacd6atagen2026-09-08 08:24 UTC620
    assert_eq!(content["info"]["thumbnail_info"]["h"], 180);
96fdf90aacd6atagen2026-09-08 08:24 UTC621
    assert_eq!(content["info"]["thumbnail_info"]["size"], 12);
96fdf90aacd6atagen2026-09-08 08:24 UTC622
    assert_eq!(
96fdf90aacd6atagen2026-09-08 08:24 UTC623
        content["info"]["thumbnail_url"],
96fdf90aacd6atagen2026-09-08 08:24 UTC624
        "mxc://localhost/video-thumb"
96fdf90aacd6atagen2026-09-08 08:24 UTC625
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC626
    assert_eq!(content["url"], "mxc://localhost/video-upload");
96fdf90aacd6atagen2026-09-08 08:24 UTC627
96fdf90aacd6atagen2026-09-08 08:24 UTC628
    let requests = harness.server.received_requests().await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC629
    assert!(requests.iter().any(|request| {
96fdf90aacd6atagen2026-09-08 08:24 UTC630
        request.url.path().ends_with("/_matrix/media/v3/upload") && request.body == b"poster bytes"
96fdf90aacd6atagen2026-09-08 08:24 UTC631
    }));
96fdf90aacd6atagen2026-09-08 08:24 UTC632
31ce2453c82fatagen2026-09-24 13:07 UTC633
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC634
}
96fdf90aacd6atagen2026-09-08 08:24 UTC635
96fdf90aacd6atagen2026-09-08 08:24 UTC636
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC637
async fn voice_upload_emits_voice_details_and_correlated_progress() {
96fdf90aacd6atagen2026-09-08 08:24 UTC638
    let mut harness = start(plain_image()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC639
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC640
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC641
        .mock_authenticated_media_config()
96fdf90aacd6atagen2026-09-08 08:24 UTC642
        .ok_default()
96fdf90aacd6atagen2026-09-08 08:24 UTC643
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC644
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC645
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC646
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC647
        .mock_upload()
96fdf90aacd6atagen2026-09-08 08:24 UTC648
        .expect_mime_type("audio/ogg")
96fdf90aacd6atagen2026-09-08 08:24 UTC649
        .respond_with(
96fdf90aacd6atagen2026-09-08 08:24 UTC650
            ResponseTemplate::new(200)
96fdf90aacd6atagen2026-09-08 08:24 UTC651
                .set_body_json(json!({ "content_uri": "mxc://localhost/voice-upload" }))
96fdf90aacd6atagen2026-09-08 08:24 UTC652
                .set_delay(Duration::from_millis(500)),
96fdf90aacd6atagen2026-09-08 08:24 UTC653
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC654
        .expect(1)
96fdf90aacd6atagen2026-09-08 08:24 UTC655
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC656
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC657
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC658
        .server
96fdf90aacd6atagen2026-09-08 08:24 UTC659
        .mock_room_send()
96fdf90aacd6atagen2026-09-08 08:24 UTC660
        .ok(event_id!("$voice-upload:localhost"))
96fdf90aacd6atagen2026-09-08 08:24 UTC661
        .expect(1)
96fdf90aacd6atagen2026-09-08 08:24 UTC662
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC663
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC664
96fdf90aacd6atagen2026-09-08 08:24 UTC665
    let data = b"voice bytes".to_vec();
96fdf90aacd6atagen2026-09-08 08:24 UTC666
    let mut upload = AttachmentUpload::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC667
        room_id!("!media:localhost").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC668
        "voice.ogg",
96fdf90aacd6atagen2026-09-08 08:24 UTC669
        "audio/ogg",
96fdf90aacd6atagen2026-09-08 08:24 UTC670
        data.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC671
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC672
    upload.details = Some(UploadDetails::Voice {
96fdf90aacd6atagen2026-09-08 08:24 UTC673
        duration_ms: 4200,
96fdf90aacd6atagen2026-09-08 08:24 UTC674
        waveform: vec![0, 512, 1024],
96fdf90aacd6atagen2026-09-08 08:24 UTC675
    });
31ce2453c82fatagen2026-09-24 13:07 UTC676
    harness.handle.upload(upload).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC677
96fdf90aacd6atagen2026-09-08 08:24 UTC678
    let pending = harness
96fdf90aacd6atagen2026-09-08 08:24 UTC679
        .handle
96fdf90aacd6atagen2026-09-08 08:24 UTC680
        .pending_send_ids(room_id!("!media:localhost"))
96fdf90aacd6atagen2026-09-08 08:24 UTC681
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC682
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC683
    assert_eq!(pending.len(), 1);
96fdf90aacd6atagen2026-09-08 08:24 UTC684
    let transaction_id = pending[0].clone();
31ce2453c82fatagen2026-09-24 13:07 UTC685
    let progress = support::receive_until(&mut harness.events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC686
        matches!(event, Event::MediaUploadProgress { transaction_id: seen, complete: true, .. } if seen == &transaction_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC687
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC688
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC689
    assert!(matches!(progress, Event::MediaUploadProgress {
96fdf90aacd6atagen2026-09-08 08:24 UTC690
        room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC691
        transaction_id: seen,
96fdf90aacd6atagen2026-09-08 08:24 UTC692
        uploaded_bytes,
96fdf90aacd6atagen2026-09-08 08:24 UTC693
        total_bytes,
96fdf90aacd6atagen2026-09-08 08:24 UTC694
        complete: true,
96fdf90aacd6atagen2026-09-08 08:24 UTC695
    } if room_id == room_id!("!media:localhost") && seen == transaction_id && uploaded_bytes == data.len() as u64 && total_bytes == data.len() as u64));
0bfa3f46f0beatagen2026-09-26 09:12 UTC696
    support::receive_until(&mut harness.events, |event| {
0bfa3f46f0beatagen2026-09-26 09:12 UTC697
        matches!(event, Event::MediaUploadFinished {
0bfa3f46f0beatagen2026-09-26 09:12 UTC698
            transaction_id: seen,
0bfa3f46f0beatagen2026-09-26 09:12 UTC699
            outcome: UploadOutcome::Sent { event_id },
0bfa3f46f0beatagen2026-09-26 09:12 UTC700
            ..
0bfa3f46f0beatagen2026-09-26 09:12 UTC701
        } if seen == &transaction_id && event_id == event_id!("$voice-upload:localhost"))
0bfa3f46f0beatagen2026-09-26 09:12 UTC702
    })
0bfa3f46f0beatagen2026-09-26 09:12 UTC703
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC704
96fdf90aacd6atagen2026-09-08 08:24 UTC705
    let content = uploaded_content(&harness.server).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC706
    assert_eq!(content["msgtype"], "m.audio");
96fdf90aacd6atagen2026-09-08 08:24 UTC707
    assert_eq!(content["info"]["duration"], 4200);
96fdf90aacd6atagen2026-09-08 08:24 UTC708
    assert_eq!(content["info"]["size"], data.len());
96fdf90aacd6atagen2026-09-08 08:24 UTC709
    assert_eq!(content["org.matrix.msc1767.audio"]["duration"], 4200);
96fdf90aacd6atagen2026-09-08 08:24 UTC710
    assert_eq!(
96fdf90aacd6atagen2026-09-08 08:24 UTC711
        content["org.matrix.msc1767.audio"]["waveform"],
96fdf90aacd6atagen2026-09-08 08:24 UTC712
        json!([0, 512, 1024])
96fdf90aacd6atagen2026-09-08 08:24 UTC713
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC714
    assert_eq!(content["org.matrix.msc3245.voice"], json!({}));
96fdf90aacd6atagen2026-09-08 08:24 UTC715
31ce2453c82fatagen2026-09-24 13:07 UTC716
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC717
}
96fdf90aacd6atagen2026-09-08 08:24 UTC718
96fdf90aacd6atagen2026-09-08 08:24 UTC719
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC720
async fn mismatched_upload_metadata_and_invalid_waveform_fail_without_uploading() {
96fdf90aacd6atagen2026-09-08 08:24 UTC721
    let mut harness = start(plain_image()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC722
    let mut mismatched = AttachmentUpload::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC723
        room_id!("!media:localhost").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC724
        "clip.mp4",
96fdf90aacd6atagen2026-09-08 08:24 UTC725
        "video/mp4",
96fdf90aacd6atagen2026-09-08 08:24 UTC726
        b"video bytes".to_vec(),
96fdf90aacd6atagen2026-09-08 08:24 UTC727
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC728
    mismatched.details = Some(UploadDetails::Image {
96fdf90aacd6atagen2026-09-08 08:24 UTC729
        width: Some(32),
96fdf90aacd6atagen2026-09-08 08:24 UTC730
        height: Some(32),
96fdf90aacd6atagen2026-09-08 08:24 UTC731
        blurhash: None,
96fdf90aacd6atagen2026-09-08 08:24 UTC732
    });
31ce2453c82fatagen2026-09-24 13:07 UTC733
    let error = harness.handle.upload(mismatched).await.unwrap_err();
31ce2453c82fatagen2026-09-24 13:07 UTC734
    assert_eq!(error.kind, ErrorKind::Invalid);
96fdf90aacd6atagen2026-09-08 08:24 UTC735
96fdf90aacd6atagen2026-09-08 08:24 UTC736
    let mut voice = AttachmentUpload::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC737
        room_id!("!media:localhost").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC738
        "voice.ogg",
96fdf90aacd6atagen2026-09-08 08:24 UTC739
        "audio/ogg",
96fdf90aacd6atagen2026-09-08 08:24 UTC740
        b"voice bytes".to_vec(),
96fdf90aacd6atagen2026-09-08 08:24 UTC741
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC742
    voice.details = Some(UploadDetails::Voice {
96fdf90aacd6atagen2026-09-08 08:24 UTC743
        duration_ms: 1000,
96fdf90aacd6atagen2026-09-08 08:24 UTC744
        waveform: vec![1025],
96fdf90aacd6atagen2026-09-08 08:24 UTC745
    });
31ce2453c82fatagen2026-09-24 13:07 UTC746
    let error = harness.handle.upload(voice).await.unwrap_err();
31ce2453c82fatagen2026-09-24 13:07 UTC747
    assert_eq!(error.kind, ErrorKind::Invalid);
96fdf90aacd6atagen2026-09-08 08:24 UTC748
96fdf90aacd6atagen2026-09-08 08:24 UTC749
    assert!(
96fdf90aacd6atagen2026-09-08 08:24 UTC750
        !harness
96fdf90aacd6atagen2026-09-08 08:24 UTC751
            .server
96fdf90aacd6atagen2026-09-08 08:24 UTC752
            .received_requests()
96fdf90aacd6atagen2026-09-08 08:24 UTC753
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC754
            .unwrap()
96fdf90aacd6atagen2026-09-08 08:24 UTC755
            .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC756
            .any(|request| { request.url.path().ends_with("/_matrix/media/v3/upload") })
96fdf90aacd6atagen2026-09-08 08:24 UTC757
    );
31ce2453c82fatagen2026-09-24 13:07 UTC758
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC759
}
96fdf90aacd6atagen2026-09-08 08:24 UTC760
96fdf90aacd6atagen2026-09-08 08:24 UTC761
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC762
async fn upload_over_media_limit_fails_without_uploading() {
96fdf90aacd6atagen2026-09-08 08:24 UTC763
    let mut harness = start_with_config(
96fdf90aacd6atagen2026-09-08 08:24 UTC764
        plain_image(),
96fdf90aacd6atagen2026-09-08 08:24 UTC765
        EngineConfig::default()
96fdf90aacd6atagen2026-09-08 08:24 UTC766
            .with_initial_history_limit(0)
96fdf90aacd6atagen2026-09-08 08:24 UTC767
            .with_max_media_bytes(4),
96fdf90aacd6atagen2026-09-08 08:24 UTC768
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC769
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC770
    let upload = AttachmentUpload::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC771
        room_id!("!media:localhost").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC772
        "large.bin",
96fdf90aacd6atagen2026-09-08 08:24 UTC773
        "application/octet-stream",
96fdf90aacd6atagen2026-09-08 08:24 UTC774
        b"too large".to_vec(),
96fdf90aacd6atagen2026-09-08 08:24 UTC775
    );
31ce2453c82fatagen2026-09-24 13:07 UTC776
    let error = harness.handle.upload(upload).await.unwrap_err();
31ce2453c82fatagen2026-09-24 13:07 UTC777
    assert_eq!(error.kind, ErrorKind::MediaTooLarge);
96fdf90aacd6atagen2026-09-08 08:24 UTC778
    assert!(
96fdf90aacd6atagen2026-09-08 08:24 UTC779
        !harness
96fdf90aacd6atagen2026-09-08 08:24 UTC780
            .server
96fdf90aacd6atagen2026-09-08 08:24 UTC781
            .received_requests()
96fdf90aacd6atagen2026-09-08 08:24 UTC782
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC783
            .unwrap()
96fdf90aacd6atagen2026-09-08 08:24 UTC784
            .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC785
            .any(|request| { request.url.path().ends_with("/_matrix/media/v3/upload") })
96fdf90aacd6atagen2026-09-08 08:24 UTC786
    );
31ce2453c82fatagen2026-09-24 13:07 UTC787
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC788
}
96fdf90aacd6atagen2026-09-08 08:24 UTC789
96fdf90aacd6atagen2026-09-08 08:24 UTC790
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC791
async fn audio_thumbnail_fails_without_uploading() {
96fdf90aacd6atagen2026-09-08 08:24 UTC792
    let mut harness = start(plain_image()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC793
    let mut upload = AttachmentUpload::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC794
        room_id!("!media:localhost").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC795
        "voice.ogg",
96fdf90aacd6atagen2026-09-08 08:24 UTC796
        "audio/ogg",
96fdf90aacd6atagen2026-09-08 08:24 UTC797
        b"voice bytes".to_vec(),
96fdf90aacd6atagen2026-09-08 08:24 UTC798
    );
31ce2453c82fatagen2026-09-24 13:07 UTC799
    upload.thumbnail = Some(UploadThumbnail::new(
31ce2453c82fatagen2026-09-24 13:07 UTC800
        b"thumbnail".to_vec(),
31ce2453c82fatagen2026-09-24 13:07 UTC801
        "image/png",
31ce2453c82fatagen2026-09-24 13:07 UTC802
        80,
31ce2453c82fatagen2026-09-24 13:07 UTC803
        80,
96fdf90aacd6atagen2026-09-08 08:24 UTC804
    ));
31ce2453c82fatagen2026-09-24 13:07 UTC805
    let error = harness.handle.upload(upload).await.unwrap_err();
31ce2453c82fatagen2026-09-24 13:07 UTC806
    assert_eq!(error.kind, ErrorKind::Invalid);
96fdf90aacd6atagen2026-09-08 08:24 UTC807
    assert!(
96fdf90aacd6atagen2026-09-08 08:24 UTC808
        !harness
96fdf90aacd6atagen2026-09-08 08:24 UTC809
            .server
96fdf90aacd6atagen2026-09-08 08:24 UTC810
            .received_requests()
96fdf90aacd6atagen2026-09-08 08:24 UTC811
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC812
            .unwrap()
96fdf90aacd6atagen2026-09-08 08:24 UTC813
            .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC814
            .any(|request| { request.url.path().ends_with("/_matrix/media/v3/upload") })
96fdf90aacd6atagen2026-09-08 08:24 UTC815
    );
31ce2453c82fatagen2026-09-24 13:07 UTC816
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC817
}
96fdf90aacd6atagen2026-09-08 08:24 UTC818
96fdf90aacd6atagen2026-09-08 08:24 UTC819
#[tokio::test]
31ce2453c82fatagen2026-09-24 13:07 UTC820
async fn invalid_upload_mime_fails_without_uploading() {
96fdf90aacd6atagen2026-09-08 08:24 UTC821
    let mut harness = start(plain_image()).await;
31ce2453c82fatagen2026-09-24 13:07 UTC822
    let upload = AttachmentUpload::new(
31ce2453c82fatagen2026-09-24 13:07 UTC823
        room_id!("!media:localhost").to_owned(),
31ce2453c82fatagen2026-09-24 13:07 UTC824
        "photo.jpg",
31ce2453c82fatagen2026-09-24 13:07 UTC825
        "not a MIME type",
31ce2453c82fatagen2026-09-24 13:07 UTC826
        b"photo data".to_vec(),
96fdf90aacd6atagen2026-09-08 08:24 UTC827
    );
31ce2453c82fatagen2026-09-24 13:07 UTC828
    let error = harness.handle.upload(upload).await.unwrap_err();
31ce2453c82fatagen2026-09-24 13:07 UTC829
    assert_eq!(error.kind, ErrorKind::Invalid);
96fdf90aacd6atagen2026-09-08 08:24 UTC830
31ce2453c82fatagen2026-09-24 13:07 UTC831
    support::shut_down(&harness.handle, &mut harness.events, harness.running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC832
}
96fdf90aacd6atagen2026-09-08 08:24 UTC833
96fdf90aacd6atagen2026-09-08 08:24 UTC834
async fn start(content: RoomMessageEventContent) -> MediaHarness {
96fdf90aacd6atagen2026-09-08 08:24 UTC835
    start_with_config(
96fdf90aacd6atagen2026-09-08 08:24 UTC836
        content,
96fdf90aacd6atagen2026-09-08 08:24 UTC837
        EngineConfig::default().with_initial_history_limit(0),
96fdf90aacd6atagen2026-09-08 08:24 UTC838
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC839
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC840
}
96fdf90aacd6atagen2026-09-08 08:24 UTC841
96fdf90aacd6atagen2026-09-08 08:24 UTC842
async fn start_with_config(content: RoomMessageEventContent, config: EngineConfig) -> MediaHarness {
96fdf90aacd6atagen2026-09-08 08:24 UTC843
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC844
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC845
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC846
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC847
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC848
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC849
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC850
    let room_id = room_id!("!media:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC851
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC852
    server.sync_joined_room(&client, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC853
    let sync_client = client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC854
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC855
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC856
31ce2453c82fatagen2026-09-24 13:07 UTC857
    handle.open_room(room_id.to_owned()).await.unwrap();
31ce2453c82fatagen2026-09-24 13:07 UTC858
    support::receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC859
        &mut events,
96fdf90aacd6atagen2026-09-08 08:24 UTC860
        |event| matches!(event, Event::TimelineReset { timeline_id: reset, .. } if reset.room_id() == room_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC861
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC862
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC863
96fdf90aacd6atagen2026-09-08 08:24 UTC864
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC865
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC866
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC867
            JoinedRoomBuilder::new(room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC868
                .add_timeline_event(EventFactory::new().event(content).sender(&ALICE)),
96fdf90aacd6atagen2026-09-08 08:24 UTC869
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC870
        .await;
31ce2453c82fatagen2026-09-24 13:07 UTC871
    let event = support::receive_until(&mut events, |event| {
31ce2453c82fatagen2026-09-24 13:07 UTC872
        support::event_messages(event)
31ce2453c82fatagen2026-09-24 13:07 UTC873
            .into_iter()
960ac0721079atagen2026-09-25 00:24 UTC874
            .any(|event| support::media_of(event).is_some())
96fdf90aacd6atagen2026-09-08 08:24 UTC875
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC876
    .await;
31ce2453c82fatagen2026-09-24 13:07 UTC877
    let media_id = support::event_messages(&event)
31ce2453c82fatagen2026-09-24 13:07 UTC878
        .into_iter()
960ac0721079atagen2026-09-25 00:24 UTC879
        .find_map(|event| support::media_of(event).map(|media| media.id.clone()))
96fdf90aacd6atagen2026-09-08 08:24 UTC880
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC881
96fdf90aacd6atagen2026-09-08 08:24 UTC882
    MediaHarness {
96fdf90aacd6atagen2026-09-08 08:24 UTC883
        server,
96fdf90aacd6atagen2026-09-08 08:24 UTC884
        client: sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC885
        handle,
96fdf90aacd6atagen2026-09-08 08:24 UTC886
        events,
96fdf90aacd6atagen2026-09-08 08:24 UTC887
        running,
96fdf90aacd6atagen2026-09-08 08:24 UTC888
        media_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC889
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC890
}
96fdf90aacd6atagen2026-09-08 08:24 UTC891
5b21b919d1e6atagen2026-09-27 11:47 UTC892
async fn sync_media(
5b21b919d1e6atagen2026-09-27 11:47 UTC893
    harness: &mut MediaHarness,
5b21b919d1e6atagen2026-09-27 11:47 UTC894
    filename: &str,
5b21b919d1e6atagen2026-09-27 11:47 UTC895
    content: RoomMessageEventContent,
5b21b919d1e6atagen2026-09-27 11:47 UTC896
) -> MediaId {
5b21b919d1e6atagen2026-09-27 11:47 UTC897
    harness
5b21b919d1e6atagen2026-09-27 11:47 UTC898
        .server
5b21b919d1e6atagen2026-09-27 11:47 UTC899
        .sync_room(
5b21b919d1e6atagen2026-09-27 11:47 UTC900
            &harness.client,
5b21b919d1e6atagen2026-09-27 11:47 UTC901
            JoinedRoomBuilder::new(room_id!("!media:localhost"))
5b21b919d1e6atagen2026-09-27 11:47 UTC902
                .add_timeline_event(EventFactory::new().event(content).sender(&ALICE)),
5b21b919d1e6atagen2026-09-27 11:47 UTC903
        )
5b21b919d1e6atagen2026-09-27 11:47 UTC904
        .await;
5b21b919d1e6atagen2026-09-27 11:47 UTC905
    let media_id = |event: &Event| {
5b21b919d1e6atagen2026-09-27 11:47 UTC906
        support::event_messages(event)
5b21b919d1e6atagen2026-09-27 11:47 UTC907
            .into_iter()
5b21b919d1e6atagen2026-09-27 11:47 UTC908
            .filter_map(support::media_of)
5b21b919d1e6atagen2026-09-27 11:47 UTC909
            .find(|media| media.filename == filename)
5b21b919d1e6atagen2026-09-27 11:47 UTC910
            .map(|media| media.id.clone())
5b21b919d1e6atagen2026-09-27 11:47 UTC911
    };
5b21b919d1e6atagen2026-09-27 11:47 UTC912
    let event =
5b21b919d1e6atagen2026-09-27 11:47 UTC913
        support::receive_until(&mut harness.events, |event| media_id(event).is_some()).await;
5b21b919d1e6atagen2026-09-27 11:47 UTC914
    media_id(&event).unwrap()
5b21b919d1e6atagen2026-09-27 11:47 UTC915
}
5b21b919d1e6atagen2026-09-27 11:47 UTC916
96fdf90aacd6atagen2026-09-08 08:24 UTC917
async fn mount_downloads(server: &MatrixMockServer) {
96fdf90aacd6atagen2026-09-08 08:24 UTC918
    server.mock_media_download().ok_image().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC919
    server.mock_authed_media_download().ok_image().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC920
}
96fdf90aacd6atagen2026-09-08 08:24 UTC921
31ce2453c82fatagen2026-09-24 13:07 UTC922
fn thumbnail_size() -> MediaSize {
31ce2453c82fatagen2026-09-24 13:07 UTC923
    MediaSize::Thumbnail {
31ce2453c82fatagen2026-09-24 13:07 UTC924
        width: 800,
31ce2453c82fatagen2026-09-24 13:07 UTC925
        height: 800,
96fdf90aacd6atagen2026-09-08 08:24 UTC926
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC927
}
96fdf90aacd6atagen2026-09-08 08:24 UTC928
31ce2453c82fatagen2026-09-24 13:07 UTC929
async fn fetch(harness: &MediaHarness, size: MediaSize) -> Arc<[u8]> {
96fdf90aacd6atagen2026-09-08 08:24 UTC930
    harness
96fdf90aacd6atagen2026-09-08 08:24 UTC931
        .handle
31ce2453c82fatagen2026-09-24 13:07 UTC932
        .media(&harness.media_id, size)
96fdf90aacd6atagen2026-09-08 08:24 UTC933
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC934
        .unwrap()
31ce2453c82fatagen2026-09-24 13:07 UTC935
        .bytes
96fdf90aacd6atagen2026-09-08 08:24 UTC936
}
96fdf90aacd6atagen2026-09-08 08:24 UTC937
96fdf90aacd6atagen2026-09-08 08:24 UTC938
async fn media_paths(server: &MatrixMockServer) -> Vec<String> {
96fdf90aacd6atagen2026-09-08 08:24 UTC939
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC940
        .received_requests()
96fdf90aacd6atagen2026-09-08 08:24 UTC941
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC942
        .unwrap()
96fdf90aacd6atagen2026-09-08 08:24 UTC943
        .into_iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC944
        .map(|request| request.url.path().to_owned())
96fdf90aacd6atagen2026-09-08 08:24 UTC945
        .filter(|path| path.contains("/media/"))
96fdf90aacd6atagen2026-09-08 08:24 UTC946
        .collect()
96fdf90aacd6atagen2026-09-08 08:24 UTC947
}
96fdf90aacd6atagen2026-09-08 08:24 UTC948
96fdf90aacd6atagen2026-09-08 08:24 UTC949
async fn uploaded_content(server: &MatrixMockServer) -> Value {
96fdf90aacd6atagen2026-09-08 08:24 UTC950
    tokio::time::timeout(Duration::from_secs(5), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC951
        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC952
            if let Some(request) = server
96fdf90aacd6atagen2026-09-08 08:24 UTC953
                .received_requests()
96fdf90aacd6atagen2026-09-08 08:24 UTC954
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC955
                .unwrap()
96fdf90aacd6atagen2026-09-08 08:24 UTC956
                .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC957
                .find(|request| request.url.path().contains("/send/m.room.message/"))
96fdf90aacd6atagen2026-09-08 08:24 UTC958
            {
96fdf90aacd6atagen2026-09-08 08:24 UTC959
                return serde_json::from_slice(&request.body).unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC960
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC961
            tokio::task::yield_now().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC962
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC963
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC964
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC965
    .expect("queued media was not sent")
96fdf90aacd6atagen2026-09-08 08:24 UTC966
}
96fdf90aacd6atagen2026-09-08 08:24 UTC967
96fdf90aacd6atagen2026-09-08 08:24 UTC968
fn file_with_thumbnail() -> RoomMessageEventContent {
96fdf90aacd6atagen2026-09-08 08:24 UTC969
    let mut info = FileInfo::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC970
    info.thumbnail_source = Some(MediaSource::Plain(
96fdf90aacd6atagen2026-09-08 08:24 UTC971
        mxc_uri!("mxc://localhost/explicit-thumbnail").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC972
    ));
96fdf90aacd6atagen2026-09-08 08:24 UTC973
    RoomMessageEventContent::new(MessageType::File(
96fdf90aacd6atagen2026-09-08 08:24 UTC974
        FileMessageEventContent::plain(
96fdf90aacd6atagen2026-09-08 08:24 UTC975
            "file.bin".to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC976
            mxc_uri!("mxc://localhost/full").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC977
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC978
        .info(Box::new(info)),
96fdf90aacd6atagen2026-09-08 08:24 UTC979
    ))
96fdf90aacd6atagen2026-09-08 08:24 UTC980
}
96fdf90aacd6atagen2026-09-08 08:24 UTC981
96fdf90aacd6atagen2026-09-08 08:24 UTC982
fn plain_image() -> RoomMessageEventContent {
96fdf90aacd6atagen2026-09-08 08:24 UTC983
    RoomMessageEventContent::new(MessageType::Image(ImageMessageEventContent::plain(
96fdf90aacd6atagen2026-09-08 08:24 UTC984
        "image.jpg".to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC985
        mxc_uri!("mxc://localhost/image").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC986
    )))
96fdf90aacd6atagen2026-09-08 08:24 UTC987
}
96fdf90aacd6atagen2026-09-08 08:24 UTC988
96fdf90aacd6atagen2026-09-08 08:24 UTC989
fn plain_audio() -> RoomMessageEventContent {
96fdf90aacd6atagen2026-09-08 08:24 UTC990
    RoomMessageEventContent::new(MessageType::Audio(AudioMessageEventContent::plain(
96fdf90aacd6atagen2026-09-08 08:24 UTC991
        "audio.ogg".to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC992
        mxc_uri!("mxc://localhost/audio").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC993
    )))
96fdf90aacd6atagen2026-09-08 08:24 UTC994
}