atagen

macltok

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