atagen

macltok

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

Blame: tests/mirror.rs

a319bae745d8atagen2026-09-26 09:59 UTC1
#![cfg(feature = "mirror")]
a319bae745d8atagen2026-09-26 09:59 UTC2
a319bae745d8atagen2026-09-26 09:59 UTC3
use std::time::Duration;
a319bae745d8atagen2026-09-26 09:59 UTC4
a319bae745d8atagen2026-09-26 09:59 UTC5
use futures_util::StreamExt;
a319bae745d8atagen2026-09-26 09:59 UTC6
use macltok::{
a319bae745d8atagen2026-09-26 09:59 UTC7
    ClientHandle, Command, Engine, EngineConfig, Event, Mirror, MirrorHandle, StateChange,
a319bae745d8atagen2026-09-26 09:59 UTC8
    Subscription, TimelineChange, TimelineCommand, TimelineDiff, TimelineId, TimelineItem, Update,
a319bae745d8atagen2026-09-26 09:59 UTC9
    UploadOutcome,
a319bae745d8atagen2026-09-26 09:59 UTC10
};
a319bae745d8atagen2026-09-26 09:59 UTC11
use matrix_sdk::{
0b8ce8dc9a1catagen2026-09-26 12:30 UTC12
    ruma::{
0b8ce8dc9a1catagen2026-09-26 12:30 UTC13
        OwnedRoomId, OwnedTransactionId, TransactionId, api::client::sync::sync_events::v5,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC14
        event_id, room_id,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC15
    },
a319bae745d8atagen2026-09-26 09:59 UTC16
    test_utils::mocks::MatrixMockServer,
a319bae745d8atagen2026-09-26 09:59 UTC17
};
a319bae745d8atagen2026-09-26 09:59 UTC18
use tokio::sync::mpsc;
a319bae745d8atagen2026-09-26 09:59 UTC19
a319bae745d8atagen2026-09-26 09:59 UTC20
struct Harness {
a319bae745d8atagen2026-09-26 09:59 UTC21
    handle: ClientHandle,
a319bae745d8atagen2026-09-26 09:59 UTC22
    mirror: MirrorHandle,
a319bae745d8atagen2026-09-26 09:59 UTC23
    inject: mpsc::UnboundedSender<Event>,
a319bae745d8atagen2026-09-26 09:59 UTC24
    room_id: OwnedRoomId,
a319bae745d8atagen2026-09-26 09:59 UTC25
    _server: MatrixMockServer,
a319bae745d8atagen2026-09-26 09:59 UTC26
}
a319bae745d8atagen2026-09-26 09:59 UTC27
a319bae745d8atagen2026-09-26 09:59 UTC28
/// the mirror reads the real engine's stream with synthetic events mixed in, so a test can
a319bae745d8atagen2026-09-26 09:59 UTC29
/// hand it a diff the engine never would
a319bae745d8atagen2026-09-26 09:59 UTC30
async fn start(config: EngineConfig, capacity: usize) -> Harness {
a319bae745d8atagen2026-09-26 09:59 UTC31
    let server = MatrixMockServer::new().await;
a319bae745d8atagen2026-09-26 09:59 UTC32
    server
a319bae745d8atagen2026-09-26 09:59 UTC33
        .mock_sliding_sync()
a319bae745d8atagen2026-09-26 09:59 UTC34
        .ok(v5::Response::new("1".to_owned()))
a319bae745d8atagen2026-09-26 09:59 UTC35
        .mount()
a319bae745d8atagen2026-09-26 09:59 UTC36
        .await;
a319bae745d8atagen2026-09-26 09:59 UTC37
    let client = server.client_builder().build().await;
a319bae745d8atagen2026-09-26 09:59 UTC38
    let room_id = room_id!("!mirror:localhost");
a319bae745d8atagen2026-09-26 09:59 UTC39
    server.mock_room_state_encryption().plain().mount().await;
a319bae745d8atagen2026-09-26 09:59 UTC40
    server.sync_joined_room(&client, room_id).await;
a319bae745d8atagen2026-09-26 09:59 UTC41
    let (engine, handle, events) =
a319bae745d8atagen2026-09-26 09:59 UTC42
        Engine::from_client(client, config.with_initial_history_limit(0))
a319bae745d8atagen2026-09-26 09:59 UTC43
            .await
a319bae745d8atagen2026-09-26 09:59 UTC44
            .unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC45
    tokio::spawn(engine.run());
a319bae745d8atagen2026-09-26 09:59 UTC46
    let (inject, mut injected) = mpsc::unbounded_channel();
a319bae745d8atagen2026-09-26 09:59 UTC47
    let injected = futures_util::stream::poll_fn(move |context| injected.poll_recv(context));
a319bae745d8atagen2026-09-26 09:59 UTC48
    let (mirror, mirror_handle) = Mirror::with_capacity(
a319bae745d8atagen2026-09-26 09:59 UTC49
        handle.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC50
        futures_util::stream::select(events, injected),
a319bae745d8atagen2026-09-26 09:59 UTC51
        capacity,
a319bae745d8atagen2026-09-26 09:59 UTC52
    );
a319bae745d8atagen2026-09-26 09:59 UTC53
    tokio::spawn(mirror.run());
a319bae745d8atagen2026-09-26 09:59 UTC54
    Harness {
a319bae745d8atagen2026-09-26 09:59 UTC55
        handle,
a319bae745d8atagen2026-09-26 09:59 UTC56
        mirror: mirror_handle,
a319bae745d8atagen2026-09-26 09:59 UTC57
        inject,
a319bae745d8atagen2026-09-26 09:59 UTC58
        room_id: room_id.to_owned(),
a319bae745d8atagen2026-09-26 09:59 UTC59
        _server: server,
a319bae745d8atagen2026-09-26 09:59 UTC60
    }
a319bae745d8atagen2026-09-26 09:59 UTC61
}
a319bae745d8atagen2026-09-26 09:59 UTC62
a319bae745d8atagen2026-09-26 09:59 UTC63
async fn next_matching(
a319bae745d8atagen2026-09-26 09:59 UTC64
    subscription: &mut Subscription,
a319bae745d8atagen2026-09-26 09:59 UTC65
    predicate: impl Fn(&StateChange) -> bool,
a319bae745d8atagen2026-09-26 09:59 UTC66
) -> Update {
a319bae745d8atagen2026-09-26 09:59 UTC67
    tokio::time::timeout(Duration::from_secs(5), async {
a319bae745d8atagen2026-09-26 09:59 UTC68
        loop {
a319bae745d8atagen2026-09-26 09:59 UTC69
            let update = subscription.next().await.expect("the mirror stopped");
a319bae745d8atagen2026-09-26 09:59 UTC70
            if predicate(&update.change) {
a319bae745d8atagen2026-09-26 09:59 UTC71
                return update;
a319bae745d8atagen2026-09-26 09:59 UTC72
            }
a319bae745d8atagen2026-09-26 09:59 UTC73
        }
a319bae745d8atagen2026-09-26 09:59 UTC74
    })
a319bae745d8atagen2026-09-26 09:59 UTC75
    .await
a319bae745d8atagen2026-09-26 09:59 UTC76
    .expect("expected update did not arrive")
a319bae745d8atagen2026-09-26 09:59 UTC77
}
a319bae745d8atagen2026-09-26 09:59 UTC78
a319bae745d8atagen2026-09-26 09:59 UTC79
fn timeline_change(
a319bae745d8atagen2026-09-26 09:59 UTC80
    change: &StateChange,
a319bae745d8atagen2026-09-26 09:59 UTC81
    id: &TimelineId,
a319bae745d8atagen2026-09-26 09:59 UTC82
    predicate: impl Fn(&TimelineChange) -> bool,
a319bae745d8atagen2026-09-26 09:59 UTC83
) -> bool {
a319bae745d8atagen2026-09-26 09:59 UTC84
    matches!(change, StateChange::Timeline { timeline_id, change } if timeline_id == id && predicate(change))
a319bae745d8atagen2026-09-26 09:59 UTC85
}
a319bae745d8atagen2026-09-26 09:59 UTC86
a319bae745d8atagen2026-09-26 09:59 UTC87
fn item(timestamp_ms: u64) -> TimelineItem {
a319bae745d8atagen2026-09-26 09:59 UTC88
    TimelineItem::DayDivider { timestamp_ms }
a319bae745d8atagen2026-09-26 09:59 UTC89
}
a319bae745d8atagen2026-09-26 09:59 UTC90
a319bae745d8atagen2026-09-26 09:59 UTC91
fn reset(timeline_id: &TimelineId, items: Vec<TimelineItem>) -> Event {
a319bae745d8atagen2026-09-26 09:59 UTC92
    Event::TimelineReset {
a319bae745d8atagen2026-09-26 09:59 UTC93
        timeline_id: timeline_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC94
        items: items.into(),
a319bae745d8atagen2026-09-26 09:59 UTC95
    }
a319bae745d8atagen2026-09-26 09:59 UTC96
}
a319bae745d8atagen2026-09-26 09:59 UTC97
a319bae745d8atagen2026-09-26 09:59 UTC98
/// the room never opens, so every resync is refused and the divergence holds still to be looked at
a319bae745d8atagen2026-09-26 09:59 UTC99
#[tokio::test]
a319bae745d8atagen2026-09-26 09:59 UTC100
async fn diverged_batch_keeps_last_good_items_until_a_reset() {
a319bae745d8atagen2026-09-26 09:59 UTC101
    let harness = start(EngineConfig::default(), 256).await;
a319bae745d8atagen2026-09-26 09:59 UTC102
    let timeline_id = TimelineId::from(harness.room_id.clone());
a319bae745d8atagen2026-09-26 09:59 UTC103
    let mut updates = harness.mirror.subscribe();
a319bae745d8atagen2026-09-26 09:59 UTC104
    harness
a319bae745d8atagen2026-09-26 09:59 UTC105
        .inject
a319bae745d8atagen2026-09-26 09:59 UTC106
        .send(reset(&timeline_id, vec![item(1), item(2)]))
a319bae745d8atagen2026-09-26 09:59 UTC107
        .unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC108
    for diffs in [
a319bae745d8atagen2026-09-26 09:59 UTC109
        vec![
a319bae745d8atagen2026-09-26 09:59 UTC110
            TimelineDiff::PushBack { value: item(3) },
a319bae745d8atagen2026-09-26 09:59 UTC111
            TimelineDiff::Remove { index: 7 },
a319bae745d8atagen2026-09-26 09:59 UTC112
        ],
a319bae745d8atagen2026-09-26 09:59 UTC113
        vec![TimelineDiff::PushBack { value: item(4) }],
a319bae745d8atagen2026-09-26 09:59 UTC114
    ] {
a319bae745d8atagen2026-09-26 09:59 UTC115
        harness
a319bae745d8atagen2026-09-26 09:59 UTC116
            .inject
a319bae745d8atagen2026-09-26 09:59 UTC117
            .send(Event::TimelineChanged {
a319bae745d8atagen2026-09-26 09:59 UTC118
                timeline_id: timeline_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC119
                diffs,
a319bae745d8atagen2026-09-26 09:59 UTC120
            })
a319bae745d8atagen2026-09-26 09:59 UTC121
            .unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC122
    }
a319bae745d8atagen2026-09-26 09:59 UTC123
    next_matching(&mut updates, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC124
        timeline_change(change, &timeline_id, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC125
            matches!(change, TimelineChange::Diverged)
a319bae745d8atagen2026-09-26 09:59 UTC126
        })
a319bae745d8atagen2026-09-26 09:59 UTC127
    })
a319bae745d8atagen2026-09-26 09:59 UTC128
    .await;
a319bae745d8atagen2026-09-26 09:59 UTC129
    let snapshot = harness.mirror.snapshot();
a319bae745d8atagen2026-09-26 09:59 UTC130
    let timeline = &snapshot.timelines[&timeline_id];
a319bae745d8atagen2026-09-26 09:59 UTC131
    assert!(timeline.diverged);
a319bae745d8atagen2026-09-26 09:59 UTC132
    assert_eq!(timeline.items, [item(1), item(2)].into_iter().collect());
a319bae745d8atagen2026-09-26 09:59 UTC133
a319bae745d8atagen2026-09-26 09:59 UTC134
    harness
a319bae745d8atagen2026-09-26 09:59 UTC135
        .inject
a319bae745d8atagen2026-09-26 09:59 UTC136
        .send(reset(&timeline_id, vec![item(5)]))
a319bae745d8atagen2026-09-26 09:59 UTC137
        .unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC138
    let update = next_matching(&mut updates, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC139
        timeline_change(change, &timeline_id, |_| true)
a319bae745d8atagen2026-09-26 09:59 UTC140
    })
a319bae745d8atagen2026-09-26 09:59 UTC141
    .await;
a319bae745d8atagen2026-09-26 09:59 UTC142
    assert!(
a319bae745d8atagen2026-09-26 09:59 UTC143
        timeline_change(&update.change, &timeline_id, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC144
            matches!(change, TimelineChange::Reset { .. })
a319bae745d8atagen2026-09-26 09:59 UTC145
        }),
a319bae745d8atagen2026-09-26 09:59 UTC146
        "a diverged timeline took {update:?}"
a319bae745d8atagen2026-09-26 09:59 UTC147
    );
a319bae745d8atagen2026-09-26 09:59 UTC148
    let snapshot = harness.mirror.snapshot();
a319bae745d8atagen2026-09-26 09:59 UTC149
    let timeline = &snapshot.timelines[&timeline_id];
a319bae745d8atagen2026-09-26 09:59 UTC150
    assert!(!timeline.diverged);
a319bae745d8atagen2026-09-26 09:59 UTC151
    assert_eq!(timeline.items, [item(5)].into_iter().collect());
a319bae745d8atagen2026-09-26 09:59 UTC152
}
a319bae745d8atagen2026-09-26 09:59 UTC153
a319bae745d8atagen2026-09-26 09:59 UTC154
#[tokio::test]
0b8ce8dc9a1catagen2026-09-26 12:30 UTC155
async fn a_diff_for_a_timeline_without_a_reset_diverges_it() {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC156
    let harness = start(EngineConfig::default(), 256).await;
0b8ce8dc9a1catagen2026-09-26 12:30 UTC157
    let timeline_id = TimelineId::from(harness.room_id.clone());
0b8ce8dc9a1catagen2026-09-26 12:30 UTC158
    let mut updates = harness.mirror.subscribe();
0b8ce8dc9a1catagen2026-09-26 12:30 UTC159
    harness
0b8ce8dc9a1catagen2026-09-26 12:30 UTC160
        .inject
0b8ce8dc9a1catagen2026-09-26 12:30 UTC161
        .send(Event::TimelineChanged {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC162
            timeline_id: timeline_id.clone(),
0b8ce8dc9a1catagen2026-09-26 12:30 UTC163
            diffs: vec![TimelineDiff::PushBack { value: item(1) }],
0b8ce8dc9a1catagen2026-09-26 12:30 UTC164
        })
0b8ce8dc9a1catagen2026-09-26 12:30 UTC165
        .unwrap();
0b8ce8dc9a1catagen2026-09-26 12:30 UTC166
    next_matching(&mut updates, |change| {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC167
        timeline_change(change, &timeline_id, |change| {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC168
            matches!(change, TimelineChange::Diverged)
0b8ce8dc9a1catagen2026-09-26 12:30 UTC169
        })
0b8ce8dc9a1catagen2026-09-26 12:30 UTC170
    })
0b8ce8dc9a1catagen2026-09-26 12:30 UTC171
    .await;
0b8ce8dc9a1catagen2026-09-26 12:30 UTC172
    let snapshot = harness.mirror.snapshot();
0b8ce8dc9a1catagen2026-09-26 12:30 UTC173
    let timeline = &snapshot.timelines[&timeline_id];
0b8ce8dc9a1catagen2026-09-26 12:30 UTC174
    assert!(timeline.diverged);
0b8ce8dc9a1catagen2026-09-26 12:30 UTC175
    assert!(timeline.items.is_empty());
0b8ce8dc9a1catagen2026-09-26 12:30 UTC176
}
0b8ce8dc9a1catagen2026-09-26 12:30 UTC177
0b8ce8dc9a1catagen2026-09-26 12:30 UTC178
#[tokio::test]
a319bae745d8atagen2026-09-26 09:59 UTC179
async fn room_and_thread_timelines_stay_apart() {
a319bae745d8atagen2026-09-26 09:59 UTC180
    let harness = start(EngineConfig::default(), 256).await;
a319bae745d8atagen2026-09-26 09:59 UTC181
    let room = TimelineId::from(harness.room_id.clone());
a319bae745d8atagen2026-09-26 09:59 UTC182
    let thread = TimelineId::thread(
a319bae745d8atagen2026-09-26 09:59 UTC183
        harness.room_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC184
        event_id!("$root:localhost").to_owned(),
a319bae745d8atagen2026-09-26 09:59 UTC185
    );
a319bae745d8atagen2026-09-26 09:59 UTC186
    let mut updates = harness.mirror.subscribe();
a319bae745d8atagen2026-09-26 09:59 UTC187
    harness.inject.send(reset(&room, vec![item(1)])).unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC188
    harness.inject.send(reset(&thread, vec![item(2)])).unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC189
    harness
a319bae745d8atagen2026-09-26 09:59 UTC190
        .inject
a319bae745d8atagen2026-09-26 09:59 UTC191
        .send(Event::TimelineChanged {
a319bae745d8atagen2026-09-26 09:59 UTC192
            timeline_id: thread.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC193
            diffs: vec![TimelineDiff::PushBack { value: item(3) }],
a319bae745d8atagen2026-09-26 09:59 UTC194
        })
a319bae745d8atagen2026-09-26 09:59 UTC195
        .unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC196
    next_matching(&mut updates, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC197
        timeline_change(change, &thread, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC198
            matches!(change, TimelineChange::Changed { .. })
a319bae745d8atagen2026-09-26 09:59 UTC199
        })
a319bae745d8atagen2026-09-26 09:59 UTC200
    })
a319bae745d8atagen2026-09-26 09:59 UTC201
    .await;
a319bae745d8atagen2026-09-26 09:59 UTC202
    let snapshot = harness.mirror.snapshot();
a319bae745d8atagen2026-09-26 09:59 UTC203
    assert_eq!(
a319bae745d8atagen2026-09-26 09:59 UTC204
        snapshot.timelines[&room].items,
a319bae745d8atagen2026-09-26 09:59 UTC205
        [item(1)].into_iter().collect()
a319bae745d8atagen2026-09-26 09:59 UTC206
    );
a319bae745d8atagen2026-09-26 09:59 UTC207
    assert_eq!(
a319bae745d8atagen2026-09-26 09:59 UTC208
        snapshot.timelines[&thread].items,
a319bae745d8atagen2026-09-26 09:59 UTC209
        [item(2), item(3)].into_iter().collect()
a319bae745d8atagen2026-09-26 09:59 UTC210
    );
a319bae745d8atagen2026-09-26 09:59 UTC211
}
a319bae745d8atagen2026-09-26 09:59 UTC212
0b8ce8dc9a1catagen2026-09-26 12:30 UTC213
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
0b8ce8dc9a1catagen2026-09-26 12:30 UTC214
async fn subscribers_joining_mid_stream_see_no_gap_and_nothing_twice() {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC215
    const CHUNKS: u64 = 64;
0b8ce8dc9a1catagen2026-09-26 12:30 UTC216
    const CHUNK: u64 = 32;
0b8ce8dc9a1catagen2026-09-26 12:30 UTC217
    let harness = start(EngineConfig::default(), 4096).await;
a319bae745d8atagen2026-09-26 09:59 UTC218
    let transaction_id = TransactionId::new();
0b8ce8dc9a1catagen2026-09-26 12:30 UTC219
    let mut followers = Vec::new();
0b8ce8dc9a1catagen2026-09-26 12:30 UTC220
    for chunk in 0..CHUNKS {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC221
        for uploaded_bytes in chunk * CHUNK + 1..=(chunk + 1) * CHUNK {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC222
            harness
0b8ce8dc9a1catagen2026-09-26 12:30 UTC223
                .inject
0b8ce8dc9a1catagen2026-09-26 12:30 UTC224
                .send(Event::MediaUploadProgress {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC225
                    room_id: harness.room_id.clone(),
0b8ce8dc9a1catagen2026-09-26 12:30 UTC226
                    transaction_id: transaction_id.clone(),
0b8ce8dc9a1catagen2026-09-26 12:30 UTC227
                    uploaded_bytes,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC228
                    total_bytes: CHUNKS * CHUNK,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC229
                    complete: false,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC230
                })
0b8ce8dc9a1catagen2026-09-26 12:30 UTC231
                .unwrap();
0b8ce8dc9a1catagen2026-09-26 12:30 UTC232
        }
0b8ce8dc9a1catagen2026-09-26 12:30 UTC233
        followers.push(tokio::spawn(follow(
0b8ce8dc9a1catagen2026-09-26 12:30 UTC234
            harness.mirror.subscribe(),
0b8ce8dc9a1catagen2026-09-26 12:30 UTC235
            transaction_id.clone(),
0b8ce8dc9a1catagen2026-09-26 12:30 UTC236
            CHUNKS * CHUNK,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC237
        )));
a319bae745d8atagen2026-09-26 09:59 UTC238
    }
0b8ce8dc9a1catagen2026-09-26 12:30 UTC239
    for follower in followers {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC240
        follower.await.unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC241
    }
0b8ce8dc9a1catagen2026-09-26 12:30 UTC242
}
a319bae745d8atagen2026-09-26 09:59 UTC243
0b8ce8dc9a1catagen2026-09-26 12:30 UTC244
async fn follow(mut updates: Subscription, transaction_id: OwnedTransactionId, total: u64) {
a319bae745d8atagen2026-09-26 09:59 UTC245
    let first = updates.next().await.unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC246
    let StateChange::State(state) = &first.change else {
a319bae745d8atagen2026-09-26 09:59 UTC247
        panic!("a subscription opened with {first:?}");
a319bae745d8atagen2026-09-26 09:59 UTC248
    };
a319bae745d8atagen2026-09-26 09:59 UTC249
    let mut seen = state
a319bae745d8atagen2026-09-26 09:59 UTC250
        .uploads
a319bae745d8atagen2026-09-26 09:59 UTC251
        .get(&transaction_id)
a319bae745d8atagen2026-09-26 09:59 UTC252
        .map_or(0, |upload| upload.uploaded_bytes);
a319bae745d8atagen2026-09-26 09:59 UTC253
    let mut sequence = first.sequence;
0b8ce8dc9a1catagen2026-09-26 12:30 UTC254
    while seen < total {
a319bae745d8atagen2026-09-26 09:59 UTC255
        let update = next_matching(&mut updates, |_| true).await;
a319bae745d8atagen2026-09-26 09:59 UTC256
        assert_eq!(update.sequence, sequence + 1);
a319bae745d8atagen2026-09-26 09:59 UTC257
        sequence = update.sequence;
a319bae745d8atagen2026-09-26 09:59 UTC258
        if let StateChange::Upload { upload, .. } = update.change {
a319bae745d8atagen2026-09-26 09:59 UTC259
            assert_eq!(upload.uploaded_bytes, seen + 1);
a319bae745d8atagen2026-09-26 09:59 UTC260
            seen += 1;
a319bae745d8atagen2026-09-26 09:59 UTC261
        }
a319bae745d8atagen2026-09-26 09:59 UTC262
    }
a319bae745d8atagen2026-09-26 09:59 UTC263
}
a319bae745d8atagen2026-09-26 09:59 UTC264
a319bae745d8atagen2026-09-26 09:59 UTC265
#[tokio::test]
a319bae745d8atagen2026-09-26 09:59 UTC266
async fn lagging_subscriber_starts_over_from_a_fresh_state() {
a319bae745d8atagen2026-09-26 09:59 UTC267
    let harness = start(EngineConfig::default(), 4).await;
a319bae745d8atagen2026-09-26 09:59 UTC268
    let transaction_id = TransactionId::new();
a319bae745d8atagen2026-09-26 09:59 UTC269
    let mut updates = harness.mirror.subscribe();
a319bae745d8atagen2026-09-26 09:59 UTC270
    assert!(matches!(
a319bae745d8atagen2026-09-26 09:59 UTC271
        updates.next().await.unwrap().change,
a319bae745d8atagen2026-09-26 09:59 UTC272
        StateChange::State(_)
a319bae745d8atagen2026-09-26 09:59 UTC273
    ));
a319bae745d8atagen2026-09-26 09:59 UTC274
    for uploaded_bytes in 1..=20 {
a319bae745d8atagen2026-09-26 09:59 UTC275
        harness
a319bae745d8atagen2026-09-26 09:59 UTC276
            .inject
a319bae745d8atagen2026-09-26 09:59 UTC277
            .send(Event::MediaUploadProgress {
a319bae745d8atagen2026-09-26 09:59 UTC278
                room_id: harness.room_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC279
                transaction_id: transaction_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC280
                uploaded_bytes,
a319bae745d8atagen2026-09-26 09:59 UTC281
                total_bytes: 20,
a319bae745d8atagen2026-09-26 09:59 UTC282
                complete: uploaded_bytes == 20,
a319bae745d8atagen2026-09-26 09:59 UTC283
            })
a319bae745d8atagen2026-09-26 09:59 UTC284
            .unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC285
    }
a319bae745d8atagen2026-09-26 09:59 UTC286
    tokio::time::timeout(Duration::from_secs(5), async {
a319bae745d8atagen2026-09-26 09:59 UTC287
        while !harness
a319bae745d8atagen2026-09-26 09:59 UTC288
            .mirror
a319bae745d8atagen2026-09-26 09:59 UTC289
            .snapshot()
a319bae745d8atagen2026-09-26 09:59 UTC290
            .uploads
a319bae745d8atagen2026-09-26 09:59 UTC291
            .get(&transaction_id)
a319bae745d8atagen2026-09-26 09:59 UTC292
            .is_some_and(|upload| upload.complete)
a319bae745d8atagen2026-09-26 09:59 UTC293
        {
a319bae745d8atagen2026-09-26 09:59 UTC294
            tokio::task::yield_now().await;
a319bae745d8atagen2026-09-26 09:59 UTC295
        }
a319bae745d8atagen2026-09-26 09:59 UTC296
    })
a319bae745d8atagen2026-09-26 09:59 UTC297
    .await
a319bae745d8atagen2026-09-26 09:59 UTC298
    .expect("the mirror never caught up");
a319bae745d8atagen2026-09-26 09:59 UTC299
a319bae745d8atagen2026-09-26 09:59 UTC300
    let update = updates.next().await.unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC301
    let StateChange::State(state) = &update.change else {
a319bae745d8atagen2026-09-26 09:59 UTC302
        panic!("a lagged subscription carried on with {update:?}");
a319bae745d8atagen2026-09-26 09:59 UTC303
    };
a319bae745d8atagen2026-09-26 09:59 UTC304
    assert!(state.uploads[&transaction_id].complete);
a319bae745d8atagen2026-09-26 09:59 UTC305
}
a319bae745d8atagen2026-09-26 09:59 UTC306
a319bae745d8atagen2026-09-26 09:59 UTC307
#[tokio::test]
a319bae745d8atagen2026-09-26 09:59 UTC308
async fn resync_does_not_deadlock_on_full_queues() {
a319bae745d8atagen2026-09-26 09:59 UTC309
    let config = EngineConfig::default()
a319bae745d8atagen2026-09-26 09:59 UTC310
        .with_command_capacity(1)
a319bae745d8atagen2026-09-26 09:59 UTC311
        .with_event_capacity(1);
a319bae745d8atagen2026-09-26 09:59 UTC312
    let harness = start(config, 1024).await;
a319bae745d8atagen2026-09-26 09:59 UTC313
    let timeline_id = TimelineId::from(harness.room_id.clone());
a319bae745d8atagen2026-09-26 09:59 UTC314
    let mut updates = harness.mirror.subscribe();
a319bae745d8atagen2026-09-26 09:59 UTC315
    harness
a319bae745d8atagen2026-09-26 09:59 UTC316
        .handle
a319bae745d8atagen2026-09-26 09:59 UTC317
        .open_room(harness.room_id.clone())
a319bae745d8atagen2026-09-26 09:59 UTC318
        .await
a319bae745d8atagen2026-09-26 09:59 UTC319
        .unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC320
    next_matching(&mut updates, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC321
        timeline_change(change, &timeline_id, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC322
            matches!(change, TimelineChange::Reset { .. })
a319bae745d8atagen2026-09-26 09:59 UTC323
        })
a319bae745d8atagen2026-09-26 09:59 UTC324
    })
a319bae745d8atagen2026-09-26 09:59 UTC325
    .await;
a319bae745d8atagen2026-09-26 09:59 UTC326
a319bae745d8atagen2026-09-26 09:59 UTC327
    let flood = tokio::spawn({
a319bae745d8atagen2026-09-26 09:59 UTC328
        let handle = harness.handle.clone();
a319bae745d8atagen2026-09-26 09:59 UTC329
        let timeline_id = timeline_id.clone();
a319bae745d8atagen2026-09-26 09:59 UTC330
        async move {
a319bae745d8atagen2026-09-26 09:59 UTC331
            loop {
a319bae745d8atagen2026-09-26 09:59 UTC332
                let command = Command::Timeline(TimelineCommand::Paginate {
a319bae745d8atagen2026-09-26 09:59 UTC333
                    timeline_id: timeline_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC334
                    limit: 1,
a319bae745d8atagen2026-09-26 09:59 UTC335
                });
a319bae745d8atagen2026-09-26 09:59 UTC336
                if handle.send(command).await.is_err() {
a319bae745d8atagen2026-09-26 09:59 UTC337
                    return;
a319bae745d8atagen2026-09-26 09:59 UTC338
                }
a319bae745d8atagen2026-09-26 09:59 UTC339
            }
a319bae745d8atagen2026-09-26 09:59 UTC340
        }
a319bae745d8atagen2026-09-26 09:59 UTC341
    });
a319bae745d8atagen2026-09-26 09:59 UTC342
    for _ in 0..10 {
a319bae745d8atagen2026-09-26 09:59 UTC343
        harness
a319bae745d8atagen2026-09-26 09:59 UTC344
            .inject
a319bae745d8atagen2026-09-26 09:59 UTC345
            .send(Event::TimelineChanged {
a319bae745d8atagen2026-09-26 09:59 UTC346
                timeline_id: timeline_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC347
                diffs: vec![TimelineDiff::Remove { index: 1000 }],
a319bae745d8atagen2026-09-26 09:59 UTC348
            })
a319bae745d8atagen2026-09-26 09:59 UTC349
            .unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC350
        next_matching(&mut updates, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC351
            timeline_change(change, &timeline_id, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC352
                matches!(change, TimelineChange::Diverged)
a319bae745d8atagen2026-09-26 09:59 UTC353
            })
a319bae745d8atagen2026-09-26 09:59 UTC354
        })
a319bae745d8atagen2026-09-26 09:59 UTC355
        .await;
a319bae745d8atagen2026-09-26 09:59 UTC356
        next_matching(&mut updates, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC357
            timeline_change(change, &timeline_id, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC358
                matches!(change, TimelineChange::Reset { .. })
a319bae745d8atagen2026-09-26 09:59 UTC359
            })
a319bae745d8atagen2026-09-26 09:59 UTC360
        })
a319bae745d8atagen2026-09-26 09:59 UTC361
        .await;
a319bae745d8atagen2026-09-26 09:59 UTC362
    }
a319bae745d8atagen2026-09-26 09:59 UTC363
    flood.abort();
a319bae745d8atagen2026-09-26 09:59 UTC364
}
a319bae745d8atagen2026-09-26 09:59 UTC365
a319bae745d8atagen2026-09-26 09:59 UTC366
#[tokio::test]
a319bae745d8atagen2026-09-26 09:59 UTC367
async fn closing_a_timeline_evicts_it() {
a319bae745d8atagen2026-09-26 09:59 UTC368
    let harness = start(EngineConfig::default(), 256).await;
a319bae745d8atagen2026-09-26 09:59 UTC369
    let timeline_id = TimelineId::from(harness.room_id.clone());
a319bae745d8atagen2026-09-26 09:59 UTC370
    let mut updates = harness.mirror.subscribe();
a319bae745d8atagen2026-09-26 09:59 UTC371
    harness
a319bae745d8atagen2026-09-26 09:59 UTC372
        .handle
a319bae745d8atagen2026-09-26 09:59 UTC373
        .open_room(harness.room_id.clone())
a319bae745d8atagen2026-09-26 09:59 UTC374
        .await
a319bae745d8atagen2026-09-26 09:59 UTC375
        .unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC376
    harness
a319bae745d8atagen2026-09-26 09:59 UTC377
        .handle
a319bae745d8atagen2026-09-26 09:59 UTC378
        .close_room(harness.room_id.clone())
a319bae745d8atagen2026-09-26 09:59 UTC379
        .await
a319bae745d8atagen2026-09-26 09:59 UTC380
        .unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC381
    next_matching(&mut updates, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC382
        timeline_change(change, &timeline_id, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC383
            matches!(change, TimelineChange::Closed)
a319bae745d8atagen2026-09-26 09:59 UTC384
        })
a319bae745d8atagen2026-09-26 09:59 UTC385
    })
a319bae745d8atagen2026-09-26 09:59 UTC386
    .await;
a319bae745d8atagen2026-09-26 09:59 UTC387
    assert!(
a319bae745d8atagen2026-09-26 09:59 UTC388
        !harness
a319bae745d8atagen2026-09-26 09:59 UTC389
            .mirror
a319bae745d8atagen2026-09-26 09:59 UTC390
            .snapshot()
a319bae745d8atagen2026-09-26 09:59 UTC391
            .timelines
a319bae745d8atagen2026-09-26 09:59 UTC392
            .contains_key(&timeline_id)
a319bae745d8atagen2026-09-26 09:59 UTC393
    );
a319bae745d8atagen2026-09-26 09:59 UTC394
}
a319bae745d8atagen2026-09-26 09:59 UTC395
a319bae745d8atagen2026-09-26 09:59 UTC396
#[tokio::test]
a319bae745d8atagen2026-09-26 09:59 UTC397
async fn an_upload_ends_on_finished() {
a319bae745d8atagen2026-09-26 09:59 UTC398
    let harness = start(EngineConfig::default(), 256).await;
a319bae745d8atagen2026-09-26 09:59 UTC399
    let transaction_id = TransactionId::new();
a319bae745d8atagen2026-09-26 09:59 UTC400
    let mut updates = harness.mirror.subscribe();
a319bae745d8atagen2026-09-26 09:59 UTC401
    harness
a319bae745d8atagen2026-09-26 09:59 UTC402
        .inject
a319bae745d8atagen2026-09-26 09:59 UTC403
        .send(Event::MediaUploadProgress {
a319bae745d8atagen2026-09-26 09:59 UTC404
            room_id: harness.room_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC405
            transaction_id: transaction_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC406
            uploaded_bytes: 5,
a319bae745d8atagen2026-09-26 09:59 UTC407
            total_bytes: 10,
a319bae745d8atagen2026-09-26 09:59 UTC408
            complete: false,
a319bae745d8atagen2026-09-26 09:59 UTC409
        })
a319bae745d8atagen2026-09-26 09:59 UTC410
        .unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC411
    next_matching(&mut updates, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC412
        matches!(change, StateChange::Upload { .. })
a319bae745d8atagen2026-09-26 09:59 UTC413
    })
a319bae745d8atagen2026-09-26 09:59 UTC414
    .await;
a319bae745d8atagen2026-09-26 09:59 UTC415
    assert!(
a319bae745d8atagen2026-09-26 09:59 UTC416
        harness
a319bae745d8atagen2026-09-26 09:59 UTC417
            .mirror
a319bae745d8atagen2026-09-26 09:59 UTC418
            .snapshot()
a319bae745d8atagen2026-09-26 09:59 UTC419
            .uploads
a319bae745d8atagen2026-09-26 09:59 UTC420
            .contains_key(&transaction_id)
a319bae745d8atagen2026-09-26 09:59 UTC421
    );
a319bae745d8atagen2026-09-26 09:59 UTC422
    harness
a319bae745d8atagen2026-09-26 09:59 UTC423
        .inject
a319bae745d8atagen2026-09-26 09:59 UTC424
        .send(Event::MediaUploadFinished {
a319bae745d8atagen2026-09-26 09:59 UTC425
            room_id: harness.room_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC426
            transaction_id: transaction_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC427
            outcome: UploadOutcome::Cancelled,
a319bae745d8atagen2026-09-26 09:59 UTC428
        })
a319bae745d8atagen2026-09-26 09:59 UTC429
        .unwrap();
a319bae745d8atagen2026-09-26 09:59 UTC430
    next_matching(&mut updates, |change| {
a319bae745d8atagen2026-09-26 09:59 UTC431
        matches!(change, StateChange::UploadFinished { .. })
a319bae745d8atagen2026-09-26 09:59 UTC432
    })
a319bae745d8atagen2026-09-26 09:59 UTC433
    .await;
a319bae745d8atagen2026-09-26 09:59 UTC434
    assert!(harness.mirror.snapshot().uploads.is_empty());
a319bae745d8atagen2026-09-26 09:59 UTC435
}