atagen

macltok

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

Blame: tests/engine.rs

31ce2453c82fatagen2026-09-24 13:07 UTC1
mod support;
31ce2453c82fatagen2026-09-24 13:07 UTC2
96fdf90aacd6atagen2026-09-08 08:24 UTC3
use std::time::Duration;
96fdf90aacd6atagen2026-09-08 08:24 UTC4
96fdf90aacd6atagen2026-09-08 08:24 UTC5
use macltok::{
31ce2453c82fatagen2026-09-24 13:07 UTC6
    ClientHandle, Command, Engine, EngineConfig, ErrorKind, Event, EventStream, HistoryState,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC7
    MediaSize, MessageContent, RecentHistoryState, RoomCommand, TimelineCommand, TimelineId,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC8
    TimelineItem,
96fdf90aacd6atagen2026-09-08 08:24 UTC9
};
96fdf90aacd6atagen2026-09-08 08:24 UTC10
use matrix_sdk::{
96fdf90aacd6atagen2026-09-08 08:24 UTC11
    ruma::{api::client::sync::sync_events::v5, event_id, mxc_uri, room_id},
96fdf90aacd6atagen2026-09-08 08:24 UTC12
    test_utils::mocks::{MatrixMockServer, RoomMessagesResponseTemplate},
96fdf90aacd6atagen2026-09-08 08:24 UTC13
};
96fdf90aacd6atagen2026-09-08 08:24 UTC14
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, event_factory::EventFactory};
31ce2453c82fatagen2026-09-24 13:07 UTC15
use support::{event_messages, receive_until, shut_down};
96fdf90aacd6atagen2026-09-08 08:24 UTC16
96fdf90aacd6atagen2026-09-08 08:24 UTC17
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC18
async fn opens_a_room_and_sends_messages() {
96fdf90aacd6atagen2026-09-08 08:24 UTC19
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC20
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC21
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC22
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC23
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC24
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC25
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC26
    let room_id = room_id!("!toolkit:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC27
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC28
    server.sync_joined_room(&client, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC29
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC30
        .mock_room_send()
96fdf90aacd6atagen2026-09-08 08:24 UTC31
        .body_matches_partial_json(serde_json::json!({ "body": "hello from macltok" }))
96fdf90aacd6atagen2026-09-08 08:24 UTC32
        .ok(event_id!("$sent:localhost"))
96fdf90aacd6atagen2026-09-08 08:24 UTC33
        .expect(1)
96fdf90aacd6atagen2026-09-08 08:24 UTC34
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC35
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC36
96fdf90aacd6atagen2026-09-08 08:24 UTC37
    let config = EngineConfig::default().with_initial_history_limit(0);
96fdf90aacd6atagen2026-09-08 08:24 UTC38
    let sync_client = client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC39
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC40
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC41
31ce2453c82fatagen2026-09-24 13:07 UTC42
    handle.open_room(room_id.to_owned()).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC43
    receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC44
        &mut events,
96fdf90aacd6atagen2026-09-08 08:24 UTC45
        |event| matches!(event, Event::TimelineReset { timeline_id: reset, .. } if reset.room_id() == room_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC46
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC47
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC48
96fdf90aacd6atagen2026-09-08 08:24 UTC49
    handle
31ce2453c82fatagen2026-09-24 13:07 UTC50
        .send_message(
31ce2453c82fatagen2026-09-24 13:07 UTC51
            room_id.to_owned().into(),
31ce2453c82fatagen2026-09-24 13:07 UTC52
            MessageContent::text("hello from macltok"),
31ce2453c82fatagen2026-09-24 13:07 UTC53
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC54
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC55
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC56
    tokio::time::timeout(Duration::from_secs(2), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC57
        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC58
            let sent = server
96fdf90aacd6atagen2026-09-08 08:24 UTC59
                .received_requests()
96fdf90aacd6atagen2026-09-08 08:24 UTC60
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC61
                .unwrap()
96fdf90aacd6atagen2026-09-08 08:24 UTC62
                .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC63
                .any(|request| {
96fdf90aacd6atagen2026-09-08 08:24 UTC64
                    request.url.path().contains("/send/m.room.message/")
96fdf90aacd6atagen2026-09-08 08:24 UTC65
                        && serde_json::from_slice::<serde_json::Value>(&request.body)
96fdf90aacd6atagen2026-09-08 08:24 UTC66
                            .is_ok_and(|body| body["body"] == "hello from macltok")
96fdf90aacd6atagen2026-09-08 08:24 UTC67
                });
96fdf90aacd6atagen2026-09-08 08:24 UTC68
            if sent {
96fdf90aacd6atagen2026-09-08 08:24 UTC69
                break;
96fdf90aacd6atagen2026-09-08 08:24 UTC70
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC71
            tokio::task::yield_now().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC72
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC73
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC74
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC75
    .expect("message did not reach the homeserver");
96fdf90aacd6atagen2026-09-08 08:24 UTC76
96fdf90aacd6atagen2026-09-08 08:24 UTC77
    server.mock_media_download().ok_image().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC78
    server.mock_authed_media_download().ok_image().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC79
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC80
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC81
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC82
            JoinedRoomBuilder::new(room_id).add_timeline_event(
96fdf90aacd6atagen2026-09-08 08:24 UTC83
                EventFactory::new()
96fdf90aacd6atagen2026-09-08 08:24 UTC84
                    .image(
96fdf90aacd6atagen2026-09-08 08:24 UTC85
                        "image.jpg".into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC86
                        mxc_uri!("mxc://localhost/image").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC87
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC88
                    .sender(&ALICE),
96fdf90aacd6atagen2026-09-08 08:24 UTC89
            ),
96fdf90aacd6atagen2026-09-08 08:24 UTC90
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC91
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC92
    let media_event = receive_until(&mut events, |event| {
31ce2453c82fatagen2026-09-24 13:07 UTC93
        event_messages(event)
31ce2453c82fatagen2026-09-24 13:07 UTC94
            .into_iter()
960ac0721079atagen2026-09-25 00:24 UTC95
            .any(|event| support::media_of(event).is_some())
96fdf90aacd6atagen2026-09-08 08:24 UTC96
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC97
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC98
    let media_id = event_messages(&media_event)
31ce2453c82fatagen2026-09-24 13:07 UTC99
        .into_iter()
960ac0721079atagen2026-09-25 00:24 UTC100
        .find_map(|event| support::media_of(event).map(|media| media.id.clone()))
96fdf90aacd6atagen2026-09-08 08:24 UTC101
        .unwrap();
31ce2453c82fatagen2026-09-24 13:07 UTC102
    let media = handle.media(&media_id, MediaSize::Full).await.unwrap();
31ce2453c82fatagen2026-09-24 13:07 UTC103
    assert!(!media.bytes.is_empty());
96fdf90aacd6atagen2026-09-08 08:24 UTC104
31ce2453c82fatagen2026-09-24 13:07 UTC105
    shut_down(&handle, &mut events, running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC106
}
96fdf90aacd6atagen2026-09-08 08:24 UTC107
96fdf90aacd6atagen2026-09-08 08:24 UTC108
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC109
async fn keeps_open_room_timelines_independent() {
96fdf90aacd6atagen2026-09-08 08:24 UTC110
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC111
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC112
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC113
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC114
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC115
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC116
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC117
    let sync_client = client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC118
    let first_id = room_id!("!first:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC119
    let second_id = room_id!("!second:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC120
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC121
    server.sync_joined_room(&client, first_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC122
    server.sync_joined_room(&client, second_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC123
    let config = EngineConfig::default().with_initial_history_limit(0);
96fdf90aacd6atagen2026-09-08 08:24 UTC124
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC125
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC126
31ce2453c82fatagen2026-09-24 13:07 UTC127
    let (first_open, second_open) = tokio::join!(
31ce2453c82fatagen2026-09-24 13:07 UTC128
        handle.open_room(first_id.to_owned()),
31ce2453c82fatagen2026-09-24 13:07 UTC129
        handle.open_room(second_id.to_owned())
31ce2453c82fatagen2026-09-24 13:07 UTC130
    );
31ce2453c82fatagen2026-09-24 13:07 UTC131
    first_open.unwrap();
31ce2453c82fatagen2026-09-24 13:07 UTC132
    second_open.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC133
    receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC134
        &mut events,
96fdf90aacd6atagen2026-09-08 08:24 UTC135
        |event| matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == first_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC136
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC137
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC138
    receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC139
        &mut events,
96fdf90aacd6atagen2026-09-08 08:24 UTC140
        |event| matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == second_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC141
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC142
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC143
    wait_for_room_subscriptions(&server, &[first_id, second_id]).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC144
96fdf90aacd6atagen2026-09-08 08:24 UTC145
    let factory = EventFactory::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC146
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC147
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC148
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC149
            JoinedRoomBuilder::new(first_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC150
                .add_timeline_event(factory.text_msg("first update").sender(&ALICE)),
96fdf90aacd6atagen2026-09-08 08:24 UTC151
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC152
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC153
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC154
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC155
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC156
            JoinedRoomBuilder::new(second_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC157
                .add_timeline_event(factory.text_msg("second update").sender(&ALICE)),
96fdf90aacd6atagen2026-09-08 08:24 UTC158
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC159
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC160
96fdf90aacd6atagen2026-09-08 08:24 UTC161
    let mut first_updated = false;
96fdf90aacd6atagen2026-09-08 08:24 UTC162
    let mut second_updated = false;
96fdf90aacd6atagen2026-09-08 08:24 UTC163
    tokio::time::timeout(Duration::from_secs(2), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC164
        while !(first_updated && second_updated) {
96fdf90aacd6atagen2026-09-08 08:24 UTC165
            let event = events.recv().await.expect("event stream closed");
96fdf90aacd6atagen2026-09-08 08:24 UTC166
            if let Event::TimelineChanged { timeline_id, .. } = &event {
96fdf90aacd6atagen2026-09-08 08:24 UTC167
                first_updated |= timeline_id.room_id() == first_id
960ac0721079atagen2026-09-25 00:24 UTC168
                    && event_messages(&event).into_iter().any(|event| {
960ac0721079atagen2026-09-25 00:24 UTC169
                        support::message(event)
960ac0721079atagen2026-09-25 00:24 UTC170
                            .is_some_and(|message| message.body == "first update")
960ac0721079atagen2026-09-25 00:24 UTC171
                    });
96fdf90aacd6atagen2026-09-08 08:24 UTC172
                second_updated |= timeline_id.room_id() == second_id
960ac0721079atagen2026-09-25 00:24 UTC173
                    && event_messages(&event).into_iter().any(|event| {
960ac0721079atagen2026-09-25 00:24 UTC174
                        support::message(event)
960ac0721079atagen2026-09-25 00:24 UTC175
                            .is_some_and(|message| message.body == "second update")
960ac0721079atagen2026-09-25 00:24 UTC176
                    });
96fdf90aacd6atagen2026-09-08 08:24 UTC177
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC178
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC179
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC180
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC181
    .expect("both retained rooms did not update");
96fdf90aacd6atagen2026-09-08 08:24 UTC182
31ce2453c82fatagen2026-09-24 13:07 UTC183
    handle.close_room(first_id.to_owned()).await.unwrap();
0bfa3f46f0beatagen2026-09-26 09:12 UTC184
    receive_until(&mut events, |event| {
0bfa3f46f0beatagen2026-09-26 09:12 UTC185
        matches!(event, Event::TimelineClosed { timeline_id } if timeline_id.room_id() == first_id)
0bfa3f46f0beatagen2026-09-26 09:12 UTC186
    })
0bfa3f46f0beatagen2026-09-26 09:12 UTC187
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC188
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC189
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC190
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC191
            JoinedRoomBuilder::new(second_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC192
                .add_timeline_event(factory.text_msg("still live").sender(&ALICE)),
96fdf90aacd6atagen2026-09-08 08:24 UTC193
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC194
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC195
    receive_until(&mut events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC196
        matches!(event, Event::TimelineChanged { timeline_id, .. } if timeline_id.room_id() == second_id)
960ac0721079atagen2026-09-25 00:24 UTC197
            && event_messages(event).into_iter().any(|event| {
960ac0721079atagen2026-09-25 00:24 UTC198
                support::message(event).is_some_and(|message| message.body == "still live")
960ac0721079atagen2026-09-25 00:24 UTC199
            })
96fdf90aacd6atagen2026-09-08 08:24 UTC200
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC201
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC202
31ce2453c82fatagen2026-09-24 13:07 UTC203
    shut_down(&handle, &mut events, running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC204
}
96fdf90aacd6atagen2026-09-08 08:24 UTC205
96fdf90aacd6atagen2026-09-08 08:24 UTC206
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC207
async fn resync_replaces_timeline_before_ack_and_preserves_following_diffs() {
96fdf90aacd6atagen2026-09-08 08:24 UTC208
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC209
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC210
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC211
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC212
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC213
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC214
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC215
    let sync_client = client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC216
    let room_id = room_id!("!resync:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC217
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC218
    server.sync_joined_room(&client, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC219
    let config = EngineConfig::default().with_initial_history_limit(0);
96fdf90aacd6atagen2026-09-08 08:24 UTC220
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC221
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC222
31ce2453c82fatagen2026-09-24 13:07 UTC223
    handle.open_room(room_id.to_owned()).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC224
    receive_until(&mut events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC225
        matches!(event,
96fdf90aacd6atagen2026-09-08 08:24 UTC226
            Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id
96fdf90aacd6atagen2026-09-08 08:24 UTC227
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC228
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC229
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC230
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC231
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC232
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC233
            JoinedRoomBuilder::new(room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC234
                .add_timeline_event(EventFactory::new().text_msg("before reset").sender(&ALICE)),
96fdf90aacd6atagen2026-09-08 08:24 UTC235
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC236
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC237
    receive_until(&mut events, |event| {
960ac0721079atagen2026-09-25 00:24 UTC238
        event_messages(event).into_iter().any(|event| {
960ac0721079atagen2026-09-25 00:24 UTC239
            support::message(event).is_some_and(|message| message.body == "before reset")
960ac0721079atagen2026-09-25 00:24 UTC240
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC241
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC242
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC243
31ce2453c82fatagen2026-09-24 13:07 UTC244
    let mut items =
31ce2453c82fatagen2026-09-24 13:07 UTC245
        resync_and_confirm_reset_first(&handle, &mut events, room_id.to_owned().into()).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC246
    assert!(items.iter().any(|item| matches!(item,
960ac0721079atagen2026-09-25 00:24 UTC247
        TimelineItem::Event(event) if support::message(event).is_some_and(|message| message.body == "before reset")
96fdf90aacd6atagen2026-09-08 08:24 UTC248
    )));
96fdf90aacd6atagen2026-09-08 08:24 UTC249
96fdf90aacd6atagen2026-09-08 08:24 UTC250
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC251
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC252
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC253
            JoinedRoomBuilder::new(room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC254
                .add_timeline_event(EventFactory::new().text_msg("after reset").sender(&ALICE)),
96fdf90aacd6atagen2026-09-08 08:24 UTC255
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC256
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC257
    tokio::time::timeout(Duration::from_secs(2), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC258
        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC259
            if let Event::TimelineChanged { timeline_id, diffs } =
96fdf90aacd6atagen2026-09-08 08:24 UTC260
                events.recv().await.expect("event stream closed")
96fdf90aacd6atagen2026-09-08 08:24 UTC261
                && timeline_id.room_id() == room_id
96fdf90aacd6atagen2026-09-08 08:24 UTC262
            {
96fdf90aacd6atagen2026-09-08 08:24 UTC263
                for diff in diffs {
96fdf90aacd6atagen2026-09-08 08:24 UTC264
                    diff.try_apply(&mut items)
96fdf90aacd6atagen2026-09-08 08:24 UTC265
                        .expect("resynced snapshot accepts following diffs");
96fdf90aacd6atagen2026-09-08 08:24 UTC266
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC267
                if items.iter().any(|item| {
96fdf90aacd6atagen2026-09-08 08:24 UTC268
                    matches!(item,
960ac0721079atagen2026-09-25 00:24 UTC269
                        TimelineItem::Event(event) if support::message(event).is_some_and(|message| message.body == "after reset")
96fdf90aacd6atagen2026-09-08 08:24 UTC270
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC271
                }) {
96fdf90aacd6atagen2026-09-08 08:24 UTC272
                    break;
96fdf90aacd6atagen2026-09-08 08:24 UTC273
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC274
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC275
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC276
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC277
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC278
    .expect("resynced timeline did not update");
96fdf90aacd6atagen2026-09-08 08:24 UTC279
31ce2453c82fatagen2026-09-24 13:07 UTC280
    shut_down(&handle, &mut events, running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC281
}
96fdf90aacd6atagen2026-09-08 08:24 UTC282
96fdf90aacd6atagen2026-09-08 08:24 UTC283
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC284
async fn closes_and_reopens_a_room_in_command_order() {
96fdf90aacd6atagen2026-09-08 08:24 UTC285
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC286
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC287
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC288
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC289
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC290
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC291
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC292
    let room_id = room_id!("!reopen:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC293
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC294
    server.sync_joined_room(&client, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC295
    let config = EngineConfig::default().with_initial_history_limit(0);
96fdf90aacd6atagen2026-09-08 08:24 UTC296
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC297
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC298
31ce2453c82fatagen2026-09-24 13:07 UTC299
    handle.open_room(room_id.to_owned()).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC300
    receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC301
        &mut events,
96fdf90aacd6atagen2026-09-08 08:24 UTC302
        |event| matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC303
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC304
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC305
31ce2453c82fatagen2026-09-24 13:07 UTC306
    let mut close = handle
31ce2453c82fatagen2026-09-24 13:07 UTC307
        .try_send(Command::Room(RoomCommand::Close(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC308
        .unwrap();
31ce2453c82fatagen2026-09-24 13:07 UTC309
    let mut replacement_open = handle
31ce2453c82fatagen2026-09-24 13:07 UTC310
        .try_send(Command::Room(RoomCommand::Open(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC311
        .unwrap();
31ce2453c82fatagen2026-09-24 13:07 UTC312
    let mut second_close = handle
31ce2453c82fatagen2026-09-24 13:07 UTC313
        .try_send(Command::Room(RoomCommand::Close(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC314
        .unwrap();
31ce2453c82fatagen2026-09-24 13:07 UTC315
    let mut final_open = handle
31ce2453c82fatagen2026-09-24 13:07 UTC316
        .try_send(Command::Room(RoomCommand::Open(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC317
        .unwrap();
31ce2453c82fatagen2026-09-24 13:07 UTC318
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC319
    // biased select drains buffered events before it reports ready replies
96fdf90aacd6atagen2026-09-08 08:24 UTC320
    let mut resets = 0;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC321
    let mut recent_active = false;
31ce2453c82fatagen2026-09-24 13:07 UTC322
    let mut order = Vec::new();
31ce2453c82fatagen2026-09-24 13:07 UTC323
    let (mut close_done, mut replacement_open_done, mut second_close_done, mut final_open_done) =
31ce2453c82fatagen2026-09-24 13:07 UTC324
        (false, false, false, false);
96fdf90aacd6atagen2026-09-08 08:24 UTC325
    tokio::time::timeout(Duration::from_secs(3), async {
31ce2453c82fatagen2026-09-24 13:07 UTC326
        while order.len() < 4 {
31ce2453c82fatagen2026-09-24 13:07 UTC327
            tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC328
                biased;
31ce2453c82fatagen2026-09-24 13:07 UTC329
                event = events.recv() => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC330
                    match event.expect("event stream closed") {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC331
                        Event::RecentHistoryChanged { room_id: changed, state } if changed == room_id => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC332
                            recent_active = matches!(state, RecentHistoryState::Loading);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC333
                        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC334
                        Event::TimelineClosed { timeline_id } if timeline_id.room_id() == room_id => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC335
                            recent_active = false;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC336
                        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC337
                        Event::TimelineReset { timeline_id, .. }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC338
                            if timeline_id.room_id() == room_id && !recent_active =>
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC339
                        {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC340
                            resets += 1;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC341
                            assert!(resets <= 2, "a room generation reset more than once");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC342
                        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC343
                        _ => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC344
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC345
                }
31ce2453c82fatagen2026-09-24 13:07 UTC346
                result = &mut close, if !close_done => {
31ce2453c82fatagen2026-09-24 13:07 UTC347
                    result.expect("close failed");
31ce2453c82fatagen2026-09-24 13:07 UTC348
                    close_done = true;
31ce2453c82fatagen2026-09-24 13:07 UTC349
                    order.push("close");
31ce2453c82fatagen2026-09-24 13:07 UTC350
                }
31ce2453c82fatagen2026-09-24 13:07 UTC351
                result = &mut replacement_open, if !replacement_open_done => {
31ce2453c82fatagen2026-09-24 13:07 UTC352
                    result.expect("replacement open failed");
31ce2453c82fatagen2026-09-24 13:07 UTC353
                    replacement_open_done = true;
31ce2453c82fatagen2026-09-24 13:07 UTC354
                    order.push("replacement_open");
31ce2453c82fatagen2026-09-24 13:07 UTC355
                }
31ce2453c82fatagen2026-09-24 13:07 UTC356
                result = &mut second_close, if !second_close_done => {
31ce2453c82fatagen2026-09-24 13:07 UTC357
                    result.expect("second close failed");
31ce2453c82fatagen2026-09-24 13:07 UTC358
                    second_close_done = true;
31ce2453c82fatagen2026-09-24 13:07 UTC359
                    order.push("second_close");
31ce2453c82fatagen2026-09-24 13:07 UTC360
                }
31ce2453c82fatagen2026-09-24 13:07 UTC361
                result = &mut final_open, if !final_open_done => {
31ce2453c82fatagen2026-09-24 13:07 UTC362
                    result.expect("final open failed");
31ce2453c82fatagen2026-09-24 13:07 UTC363
                    final_open_done = true;
31ce2453c82fatagen2026-09-24 13:07 UTC364
                    order.push("final_open");
96fdf90aacd6atagen2026-09-08 08:24 UTC365
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC366
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC367
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC368
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC369
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC370
    .expect("room lifecycle commands did not complete");
31ce2453c82fatagen2026-09-24 13:07 UTC371
    assert_eq!(
31ce2453c82fatagen2026-09-24 13:07 UTC372
        order,
31ce2453c82fatagen2026-09-24 13:07 UTC373
        ["close", "replacement_open", "second_close", "final_open"]
31ce2453c82fatagen2026-09-24 13:07 UTC374
    );
31ce2453c82fatagen2026-09-24 13:07 UTC375
    assert_eq!(resets, 2, "each open should have reset the timeline once");
31ce2453c82fatagen2026-09-24 13:07 UTC376
31ce2453c82fatagen2026-09-24 13:07 UTC377
    shut_down(&handle, &mut events, running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC378
}
96fdf90aacd6atagen2026-09-08 08:24 UTC379
96fdf90aacd6atagen2026-09-08 08:24 UTC380
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC381
async fn thread_command_waits_for_opening_timeline() {
96fdf90aacd6atagen2026-09-08 08:24 UTC382
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC383
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC384
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC385
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC386
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC387
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC388
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC389
    let room_id = room_id!("!thread:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC390
    let root = event_id!("$thread-root:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC391
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC392
    server.sync_joined_room(&client, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC393
    let (engine, handle, mut events) = Engine::from_client(
96fdf90aacd6atagen2026-09-08 08:24 UTC394
        client,
96fdf90aacd6atagen2026-09-08 08:24 UTC395
        EngineConfig::default().with_initial_history_limit(0),
96fdf90aacd6atagen2026-09-08 08:24 UTC396
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC397
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC398
    .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC399
    let running = tokio::spawn(engine.run());
31ce2453c82fatagen2026-09-24 13:07 UTC400
    handle.open_room(room_id.to_owned()).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC401
31ce2453c82fatagen2026-09-24 13:07 UTC402
    let mut open = handle
31ce2453c82fatagen2026-09-24 13:07 UTC403
        .try_send(Command::Timeline(TimelineCommand::OpenThread {
96fdf90aacd6atagen2026-09-08 08:24 UTC404
            room_id: room_id.to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC405
            root_event_id: root.to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC406
        }))
96fdf90aacd6atagen2026-09-08 08:24 UTC407
        .unwrap();
31ce2453c82fatagen2026-09-24 13:07 UTC408
    let mut send = handle
31ce2453c82fatagen2026-09-24 13:07 UTC409
        .try_send(Command::Timeline(TimelineCommand::Send {
96fdf90aacd6atagen2026-09-08 08:24 UTC410
            timeline_id: TimelineId::thread(room_id.to_owned(), root.to_owned()),
96fdf90aacd6atagen2026-09-08 08:24 UTC411
            content: MessageContent::text(""),
96fdf90aacd6atagen2026-09-08 08:24 UTC412
        }))
96fdf90aacd6atagen2026-09-08 08:24 UTC413
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC414
    let mut open_succeeded = false;
31ce2453c82fatagen2026-09-24 13:07 UTC415
    let mut send_done = false;
96fdf90aacd6atagen2026-09-08 08:24 UTC416
    tokio::time::timeout(Duration::from_secs(3), async {
31ce2453c82fatagen2026-09-24 13:07 UTC417
        while !send_done {
31ce2453c82fatagen2026-09-24 13:07 UTC418
            tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC419
                biased;
31ce2453c82fatagen2026-09-24 13:07 UTC420
                result = &mut open, if !open_succeeded => {
31ce2453c82fatagen2026-09-24 13:07 UTC421
                    result.expect("thread open failed");
31ce2453c82fatagen2026-09-24 13:07 UTC422
                    open_succeeded = true;
96fdf90aacd6atagen2026-09-08 08:24 UTC423
                }
31ce2453c82fatagen2026-09-24 13:07 UTC424
                result = &mut send, if !send_done => {
31ce2453c82fatagen2026-09-24 13:07 UTC425
                    assert!(open_succeeded, "thread command completed before opening");
31ce2453c82fatagen2026-09-24 13:07 UTC426
                    let error = result.expect_err("empty body send unexpectedly succeeded");
31ce2453c82fatagen2026-09-24 13:07 UTC427
                    assert_eq!(error.kind, ErrorKind::EmptyBody);
31ce2453c82fatagen2026-09-24 13:07 UTC428
                    send_done = true;
96fdf90aacd6atagen2026-09-08 08:24 UTC429
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC430
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC431
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC432
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC433
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC434
    .expect("thread commands did not complete");
31ce2453c82fatagen2026-09-24 13:07 UTC435
31ce2453c82fatagen2026-09-24 13:07 UTC436
    resync_and_confirm_reset_first(
31ce2453c82fatagen2026-09-24 13:07 UTC437
        &handle,
31ce2453c82fatagen2026-09-24 13:07 UTC438
        &mut events,
31ce2453c82fatagen2026-09-24 13:07 UTC439
        TimelineId::thread(room_id.to_owned(), root.to_owned()),
31ce2453c82fatagen2026-09-24 13:07 UTC440
    )
31ce2453c82fatagen2026-09-24 13:07 UTC441
    .await;
31ce2453c82fatagen2026-09-24 13:07 UTC442
31ce2453c82fatagen2026-09-24 13:07 UTC443
    shut_down(&handle, &mut events, running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC444
}
96fdf90aacd6atagen2026-09-08 08:24 UTC445
96fdf90aacd6atagen2026-09-08 08:24 UTC446
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC447
async fn pagination_reports_and_remembers_start_of_history() {
96fdf90aacd6atagen2026-09-08 08:24 UTC448
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC449
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC450
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC451
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC452
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC453
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC454
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC455
    let sync_client = client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC456
    let room_id = room_id!("!history:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC457
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC458
    server.sync_joined_room(&client, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC459
    let config = EngineConfig::default().with_initial_history_limit(0);
96fdf90aacd6atagen2026-09-08 08:24 UTC460
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC461
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC462
31ce2453c82fatagen2026-09-24 13:07 UTC463
    handle.open_room(room_id.to_owned()).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC464
    receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC465
        &mut events,
96fdf90aacd6atagen2026-09-08 08:24 UTC466
        |event| matches!(event, Event::TimelineReset { timeline_id: reset, .. } if reset.room_id() == room_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC467
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC468
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC469
96fdf90aacd6atagen2026-09-08 08:24 UTC470
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC471
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC472
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC473
            JoinedRoomBuilder::new(room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC474
                .add_timeline_event(EventFactory::new().text_msg("latest").sender(&ALICE))
96fdf90aacd6atagen2026-09-08 08:24 UTC475
                .set_timeline_limited()
96fdf90aacd6atagen2026-09-08 08:24 UTC476
                .set_timeline_prev_batch("before-latest"),
96fdf90aacd6atagen2026-09-08 08:24 UTC477
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC478
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC479
    receive_until(&mut events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC480
        matches!(event, Event::TimelineChanged { timeline_id: changed, .. } if changed.room_id() == room_id)
960ac0721079atagen2026-09-25 00:24 UTC481
            && event_messages(event).into_iter().any(|event| {
960ac0721079atagen2026-09-25 00:24 UTC482
                support::message(event).is_some_and(|message| message.body == "latest")
960ac0721079atagen2026-09-25 00:24 UTC483
            })
96fdf90aacd6atagen2026-09-08 08:24 UTC484
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC485
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC486
96fdf90aacd6atagen2026-09-08 08:24 UTC487
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC488
        .mock_room_messages()
96fdf90aacd6atagen2026-09-08 08:24 UTC489
        .match_from("before-latest")
96fdf90aacd6atagen2026-09-08 08:24 UTC490
        .ok(RoomMessagesResponseTemplate::default())
96fdf90aacd6atagen2026-09-08 08:24 UTC491
        .mock_once()
96fdf90aacd6atagen2026-09-08 08:24 UTC492
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC493
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC494
    let mut reached_start = false;
96fdf90aacd6atagen2026-09-08 08:24 UTC495
    for _ in 0..3 {
96fdf90aacd6atagen2026-09-08 08:24 UTC496
        handle
31ce2453c82fatagen2026-09-24 13:07 UTC497
            .paginate(room_id.to_owned().into(), 10)
96fdf90aacd6atagen2026-09-08 08:24 UTC498
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC499
            .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC500
        receive_history(&mut events, room_id, HistoryState::Loading).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC501
        match receive_history_terminal(&mut events, room_id).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC502
            HistoryState::StartReached => {
96fdf90aacd6atagen2026-09-08 08:24 UTC503
                reached_start = true;
96fdf90aacd6atagen2026-09-08 08:24 UTC504
                break;
96fdf90aacd6atagen2026-09-08 08:24 UTC505
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC506
            HistoryState::Idle => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC507
            state => panic!("unexpected pagination state {state:?}"),
96fdf90aacd6atagen2026-09-08 08:24 UTC508
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC509
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC510
    assert!(
96fdf90aacd6atagen2026-09-08 08:24 UTC511
        reached_start,
96fdf90aacd6atagen2026-09-08 08:24 UTC512
        "pagination did not reach the mocked room start"
96fdf90aacd6atagen2026-09-08 08:24 UTC513
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC514
96fdf90aacd6atagen2026-09-08 08:24 UTC515
    handle
31ce2453c82fatagen2026-09-24 13:07 UTC516
        .paginate(room_id.to_owned().into(), 10)
96fdf90aacd6atagen2026-09-08 08:24 UTC517
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC518
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC519
    receive_history(&mut events, room_id, HistoryState::StartReached).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC520
31ce2453c82fatagen2026-09-24 13:07 UTC521
    shut_down(&handle, &mut events, running).await;
31ce2453c82fatagen2026-09-24 13:07 UTC522
}
31ce2453c82fatagen2026-09-24 13:07 UTC523
31ce2453c82fatagen2026-09-24 13:07 UTC524
/// resyncs `timeline_id` and confirms its `TimelineReset` was already on the
31ce2453c82fatagen2026-09-24 13:07 UTC525
/// stream by the time the resync command answered, racing the two so we
31ce2453c82fatagen2026-09-24 13:07 UTC526
/// don't have to drain the whole stream first
31ce2453c82fatagen2026-09-24 13:07 UTC527
async fn resync_and_confirm_reset_first(
31ce2453c82fatagen2026-09-24 13:07 UTC528
    handle: &ClientHandle,
31ce2453c82fatagen2026-09-24 13:07 UTC529
    events: &mut EventStream,
31ce2453c82fatagen2026-09-24 13:07 UTC530
    timeline_id: TimelineId,
d5d8097c3afdatagen2026-09-26 09:33 UTC531
) -> macltok::imbl::Vector<TimelineItem> {
31ce2453c82fatagen2026-09-24 13:07 UTC532
    let mut pending = handle
31ce2453c82fatagen2026-09-24 13:07 UTC533
        .send(Command::Timeline(TimelineCommand::Resync {
31ce2453c82fatagen2026-09-24 13:07 UTC534
            timeline_id: timeline_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC535
        }))
96fdf90aacd6atagen2026-09-08 08:24 UTC536
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC537
        .unwrap();
31ce2453c82fatagen2026-09-24 13:07 UTC538
    let mut reset_items = None;
31ce2453c82fatagen2026-09-24 13:07 UTC539
    tokio::time::timeout(Duration::from_secs(3), async {
31ce2453c82fatagen2026-09-24 13:07 UTC540
        loop {
31ce2453c82fatagen2026-09-24 13:07 UTC541
            tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC542
                biased;
31ce2453c82fatagen2026-09-24 13:07 UTC543
                event = events.recv() => {
31ce2453c82fatagen2026-09-24 13:07 UTC544
                    if let Event::TimelineReset { timeline_id: reset_id, items } = event.expect("event stream closed")
31ce2453c82fatagen2026-09-24 13:07 UTC545
                        && reset_id == timeline_id
31ce2453c82fatagen2026-09-24 13:07 UTC546
                    {
31ce2453c82fatagen2026-09-24 13:07 UTC547
                        reset_items = Some(items);
31ce2453c82fatagen2026-09-24 13:07 UTC548
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC549
                }
31ce2453c82fatagen2026-09-24 13:07 UTC550
                result = &mut pending => {
31ce2453c82fatagen2026-09-24 13:07 UTC551
                    result.expect("resync failed");
31ce2453c82fatagen2026-09-24 13:07 UTC552
                    break;
31ce2453c82fatagen2026-09-24 13:07 UTC553
                }
31ce2453c82fatagen2026-09-24 13:07 UTC554
            }
31ce2453c82fatagen2026-09-24 13:07 UTC555
        }
31ce2453c82fatagen2026-09-24 13:07 UTC556
    })
31ce2453c82fatagen2026-09-24 13:07 UTC557
    .await
31ce2453c82fatagen2026-09-24 13:07 UTC558
    .expect("resync did not complete");
31ce2453c82fatagen2026-09-24 13:07 UTC559
    reset_items.expect("resync completed before its reset")
96fdf90aacd6atagen2026-09-08 08:24 UTC560
}
96fdf90aacd6atagen2026-09-08 08:24 UTC561
96fdf90aacd6atagen2026-09-08 08:24 UTC562
async fn receive_history(
31ce2453c82fatagen2026-09-24 13:07 UTC563
    events: &mut EventStream,
96fdf90aacd6atagen2026-09-08 08:24 UTC564
    room_id: &matrix_sdk::ruma::RoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC565
    expected: HistoryState,
96fdf90aacd6atagen2026-09-08 08:24 UTC566
) {
96fdf90aacd6atagen2026-09-08 08:24 UTC567
    receive_until(events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC568
        matches!(event, Event::HistoryChanged { timeline_id: changed, state } if changed.room_id() == room_id && state == &expected)
96fdf90aacd6atagen2026-09-08 08:24 UTC569
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC570
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC571
}
96fdf90aacd6atagen2026-09-08 08:24 UTC572
96fdf90aacd6atagen2026-09-08 08:24 UTC573
async fn receive_history_terminal(
31ce2453c82fatagen2026-09-24 13:07 UTC574
    events: &mut EventStream,
96fdf90aacd6atagen2026-09-08 08:24 UTC575
    room_id: &matrix_sdk::ruma::RoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC576
) -> HistoryState {
96fdf90aacd6atagen2026-09-08 08:24 UTC577
    match receive_until(events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC578
        matches!(event, Event::HistoryChanged { timeline_id: changed, state } if changed.room_id() == room_id && !matches!(state, HistoryState::Loading))
96fdf90aacd6atagen2026-09-08 08:24 UTC579
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC580
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC581
    {
96fdf90aacd6atagen2026-09-08 08:24 UTC582
        Event::HistoryChanged { state, .. } => state,
96fdf90aacd6atagen2026-09-08 08:24 UTC583
        _ => unreachable!(),
96fdf90aacd6atagen2026-09-08 08:24 UTC584
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC585
}
96fdf90aacd6atagen2026-09-08 08:24 UTC586
96fdf90aacd6atagen2026-09-08 08:24 UTC587
async fn wait_for_room_subscriptions(
96fdf90aacd6atagen2026-09-08 08:24 UTC588
    server: &MatrixMockServer,
96fdf90aacd6atagen2026-09-08 08:24 UTC589
    room_ids: &[&matrix_sdk::ruma::RoomId],
96fdf90aacd6atagen2026-09-08 08:24 UTC590
) {
96fdf90aacd6atagen2026-09-08 08:24 UTC591
    tokio::time::timeout(Duration::from_secs(2), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC592
        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC593
            let found = server
96fdf90aacd6atagen2026-09-08 08:24 UTC594
                .received_requests()
96fdf90aacd6atagen2026-09-08 08:24 UTC595
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC596
                .unwrap()
96fdf90aacd6atagen2026-09-08 08:24 UTC597
                .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC598
                .filter_map(|request| {
96fdf90aacd6atagen2026-09-08 08:24 UTC599
                    serde_json::from_slice::<serde_json::Value>(&request.body).ok()
96fdf90aacd6atagen2026-09-08 08:24 UTC600
                })
96fdf90aacd6atagen2026-09-08 08:24 UTC601
                .filter_map(|body| {
96fdf90aacd6atagen2026-09-08 08:24 UTC602
                    body.get("room_subscriptions")
96fdf90aacd6atagen2026-09-08 08:24 UTC603
                        .and_then(|value| value.as_object())
96fdf90aacd6atagen2026-09-08 08:24 UTC604
                        .cloned()
96fdf90aacd6atagen2026-09-08 08:24 UTC605
                })
96fdf90aacd6atagen2026-09-08 08:24 UTC606
                .any(|subscriptions| {
96fdf90aacd6atagen2026-09-08 08:24 UTC607
                    room_ids
96fdf90aacd6atagen2026-09-08 08:24 UTC608
                        .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC609
                        .all(|room_id| subscriptions.contains_key(room_id.as_str()))
96fdf90aacd6atagen2026-09-08 08:24 UTC610
                });
96fdf90aacd6atagen2026-09-08 08:24 UTC611
            if found {
96fdf90aacd6atagen2026-09-08 08:24 UTC612
                break;
96fdf90aacd6atagen2026-09-08 08:24 UTC613
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC614
            tokio::task::yield_now().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC615
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC616
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC617
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC618
    .expect("Sliding Sync did not retain every open room subscription");
96fdf90aacd6atagen2026-09-08 08:24 UTC619
}