atagen

macltok

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