atagen

macltok

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

Blame: tests/media.rs

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