atagen

macltok

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