atagen

macltok

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

Blame: tests/engine.rs

96fdf90aacd6atagen2026-09-08 08:24 UTC1
use std::time::Duration;
96fdf90aacd6atagen2026-09-08 08:24 UTC2
96fdf90aacd6atagen2026-09-08 08:24 UTC3
use macltok::{
96fdf90aacd6atagen2026-09-08 08:24 UTC4
    Command, CommandErrorKind, Engine, EngineConfig, Event, HistoryState, LifecycleCommand,
96fdf90aacd6atagen2026-09-08 08:24 UTC5
    MediaCommand, MessageContent, RoomCommand, TimelineCommand, TimelineDiff, TimelineId,
96fdf90aacd6atagen2026-09-08 08:24 UTC6
    TimelineItem,
96fdf90aacd6atagen2026-09-08 08:24 UTC7
};
96fdf90aacd6atagen2026-09-08 08:24 UTC8
use matrix_sdk::{
96fdf90aacd6atagen2026-09-08 08:24 UTC9
    ruma::{api::client::sync::sync_events::v5, event_id, mxc_uri, room_id},
96fdf90aacd6atagen2026-09-08 08:24 UTC10
    test_utils::mocks::{MatrixMockServer, RoomMessagesResponseTemplate},
96fdf90aacd6atagen2026-09-08 08:24 UTC11
};
96fdf90aacd6atagen2026-09-08 08:24 UTC12
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, event_factory::EventFactory};
96fdf90aacd6atagen2026-09-08 08:24 UTC13
96fdf90aacd6atagen2026-09-08 08:24 UTC14
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC15
async fn opens_a_room_and_sends_messages() {
96fdf90aacd6atagen2026-09-08 08:24 UTC16
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC17
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC18
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC19
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC20
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC21
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC22
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC23
    let room_id = room_id!("!toolkit:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC24
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC25
    server.sync_joined_room(&client, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC26
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC27
        .mock_room_send()
96fdf90aacd6atagen2026-09-08 08:24 UTC28
        .body_matches_partial_json(serde_json::json!({ "body": "hello from macltok" }))
96fdf90aacd6atagen2026-09-08 08:24 UTC29
        .ok(event_id!("$sent:localhost"))
96fdf90aacd6atagen2026-09-08 08:24 UTC30
        .expect(1)
96fdf90aacd6atagen2026-09-08 08:24 UTC31
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC32
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC33
96fdf90aacd6atagen2026-09-08 08:24 UTC34
    let config = EngineConfig::default().with_initial_history_limit(0);
96fdf90aacd6atagen2026-09-08 08:24 UTC35
    let sync_client = client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC36
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC37
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC38
96fdf90aacd6atagen2026-09-08 08:24 UTC39
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC40
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC41
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC42
        .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
96fdf90aacd6atagen2026-09-08 08:24 UTC50
        .send(Command::Timeline(TimelineCommand::Send {
96fdf90aacd6atagen2026-09-08 08:24 UTC51
            timeline_id: room_id.to_owned().into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC52
            content: MessageContent::text("hello from macltok"),
96fdf90aacd6atagen2026-09-08 08:24 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| {
96fdf90aacd6atagen2026-09-08 08:24 UTC93
        event_messages(event).any(|message| message.media.is_some())
96fdf90aacd6atagen2026-09-08 08:24 UTC94
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC95
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC96
    let media_id = event_messages(&media_event)
96fdf90aacd6atagen2026-09-08 08:24 UTC97
        .find_map(|message| message.media.as_ref().map(|media| media.id.clone()))
96fdf90aacd6atagen2026-09-08 08:24 UTC98
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC99
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC100
        .send(Command::Media(MediaCommand::Fetch {
96fdf90aacd6atagen2026-09-08 08:24 UTC101
            id: media_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC102
            size: macltok::MediaSize::Full,
96fdf90aacd6atagen2026-09-08 08:24 UTC103
        }))
96fdf90aacd6atagen2026-09-08 08:24 UTC104
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC105
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC106
    let media = receive_until(&mut events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC107
        matches!(event, Event::MediaReady { media_id: received, .. } if received == &media_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC108
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC109
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC110
    assert!(matches!(media, Event::MediaReady { bytes, .. } if !bytes.is_empty()));
96fdf90aacd6atagen2026-09-08 08:24 UTC111
96fdf90aacd6atagen2026-09-08 08:24 UTC112
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC113
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
96fdf90aacd6atagen2026-09-08 08:24 UTC114
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC115
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC116
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC117
    running.await.unwrap().unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC118
}
96fdf90aacd6atagen2026-09-08 08:24 UTC119
96fdf90aacd6atagen2026-09-08 08:24 UTC120
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC121
async fn keeps_open_room_timelines_independent() {
96fdf90aacd6atagen2026-09-08 08:24 UTC122
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC123
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC124
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC125
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC126
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC127
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC128
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC129
    let sync_client = client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC130
    let first_id = room_id!("!first:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC131
    let second_id = room_id!("!second:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC132
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC133
    server.sync_joined_room(&client, first_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC134
    server.sync_joined_room(&client, second_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC135
    let config = EngineConfig::default().with_initial_history_limit(0);
96fdf90aacd6atagen2026-09-08 08:24 UTC136
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC137
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC138
96fdf90aacd6atagen2026-09-08 08:24 UTC139
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC140
        .send(Command::Room(RoomCommand::Open(first_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC141
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC142
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC143
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC144
        .send(Command::Room(RoomCommand::Open(second_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC145
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC146
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC147
    receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC148
        &mut events,
96fdf90aacd6atagen2026-09-08 08:24 UTC149
        |event| matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == first_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC150
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC151
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC152
    receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC153
        &mut events,
96fdf90aacd6atagen2026-09-08 08:24 UTC154
        |event| matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == second_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC155
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC156
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC157
    wait_for_room_subscriptions(&server, &[first_id, second_id]).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC158
96fdf90aacd6atagen2026-09-08 08:24 UTC159
    let factory = EventFactory::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC160
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC161
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC162
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC163
            JoinedRoomBuilder::new(first_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC164
                .add_timeline_event(factory.text_msg("first update").sender(&ALICE)),
96fdf90aacd6atagen2026-09-08 08:24 UTC165
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC166
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC167
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC168
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC169
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC170
            JoinedRoomBuilder::new(second_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC171
                .add_timeline_event(factory.text_msg("second update").sender(&ALICE)),
96fdf90aacd6atagen2026-09-08 08:24 UTC172
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC173
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC174
96fdf90aacd6atagen2026-09-08 08:24 UTC175
    let mut first_updated = false;
96fdf90aacd6atagen2026-09-08 08:24 UTC176
    let mut second_updated = false;
96fdf90aacd6atagen2026-09-08 08:24 UTC177
    tokio::time::timeout(Duration::from_secs(2), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC178
        while !(first_updated && second_updated) {
96fdf90aacd6atagen2026-09-08 08:24 UTC179
            let event = events.recv().await.expect("event stream closed");
96fdf90aacd6atagen2026-09-08 08:24 UTC180
            if let Event::TimelineChanged { timeline_id, .. } = &event {
96fdf90aacd6atagen2026-09-08 08:24 UTC181
                first_updated |= timeline_id.room_id() == first_id
96fdf90aacd6atagen2026-09-08 08:24 UTC182
                    && event_messages(&event).any(|message| message.body == "first update");
96fdf90aacd6atagen2026-09-08 08:24 UTC183
                second_updated |= timeline_id.room_id() == second_id
96fdf90aacd6atagen2026-09-08 08:24 UTC184
                    && event_messages(&event).any(|message| message.body == "second update");
96fdf90aacd6atagen2026-09-08 08:24 UTC185
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC186
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC187
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC188
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC189
    .expect("both retained rooms did not update");
96fdf90aacd6atagen2026-09-08 08:24 UTC190
96fdf90aacd6atagen2026-09-08 08:24 UTC191
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC192
        .send(Command::Room(RoomCommand::Close(first_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC193
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC194
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC195
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC196
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC197
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC198
            JoinedRoomBuilder::new(second_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC199
                .add_timeline_event(factory.text_msg("still live").sender(&ALICE)),
96fdf90aacd6atagen2026-09-08 08:24 UTC200
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC201
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC202
    receive_until(&mut events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC203
        matches!(event, Event::TimelineChanged { timeline_id, .. } if timeline_id.room_id() == second_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC204
            && event_messages(event).any(|message| message.body == "still live")
96fdf90aacd6atagen2026-09-08 08:24 UTC205
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC206
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC207
96fdf90aacd6atagen2026-09-08 08:24 UTC208
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC209
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
96fdf90aacd6atagen2026-09-08 08:24 UTC210
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC211
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC212
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC213
    running.await.unwrap().unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC214
}
96fdf90aacd6atagen2026-09-08 08:24 UTC215
96fdf90aacd6atagen2026-09-08 08:24 UTC216
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC217
async fn resync_replaces_timeline_before_ack_and_preserves_following_diffs() {
96fdf90aacd6atagen2026-09-08 08:24 UTC218
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC219
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC220
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC221
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC222
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC223
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC224
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC225
    let sync_client = client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC226
    let room_id = room_id!("!resync:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC227
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC228
    server.sync_joined_room(&client, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC229
    let config = EngineConfig::default().with_initial_history_limit(0);
96fdf90aacd6atagen2026-09-08 08:24 UTC230
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC231
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC232
96fdf90aacd6atagen2026-09-08 08:24 UTC233
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC234
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC235
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC236
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC237
    receive_until(&mut events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC238
        matches!(event,
96fdf90aacd6atagen2026-09-08 08:24 UTC239
            Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id
96fdf90aacd6atagen2026-09-08 08:24 UTC240
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC241
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC242
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC243
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC244
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC245
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC246
            JoinedRoomBuilder::new(room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC247
                .add_timeline_event(EventFactory::new().text_msg("before reset").sender(&ALICE)),
96fdf90aacd6atagen2026-09-08 08:24 UTC248
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC249
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC250
    receive_until(&mut events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC251
        event_messages(event).any(|message| message.body == "before reset")
96fdf90aacd6atagen2026-09-08 08:24 UTC252
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC253
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC254
96fdf90aacd6atagen2026-09-08 08:24 UTC255
    let id = handle
96fdf90aacd6atagen2026-09-08 08:24 UTC256
        .send(Command::Timeline(TimelineCommand::Resync {
96fdf90aacd6atagen2026-09-08 08:24 UTC257
            timeline_id: room_id.to_owned().into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC258
        }))
96fdf90aacd6atagen2026-09-08 08:24 UTC259
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC260
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC261
    let mut reset = None;
96fdf90aacd6atagen2026-09-08 08:24 UTC262
    tokio::time::timeout(Duration::from_secs(2), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC263
        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC264
            match events.recv().await.expect("event stream closed") {
96fdf90aacd6atagen2026-09-08 08:24 UTC265
                Event::TimelineReset { timeline_id, items } if timeline_id.room_id() == room_id => {
96fdf90aacd6atagen2026-09-08 08:24 UTC266
                    reset = Some(items);
96fdf90aacd6atagen2026-09-08 08:24 UTC267
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC268
                Event::CommandSucceeded { id: completed, .. } if completed == id => {
96fdf90aacd6atagen2026-09-08 08:24 UTC269
                    assert!(reset.is_some(), "resync completed before its reset");
96fdf90aacd6atagen2026-09-08 08:24 UTC270
                    break;
96fdf90aacd6atagen2026-09-08 08:24 UTC271
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC272
                Event::CommandFailed(failure) if failure.id == id => {
96fdf90aacd6atagen2026-09-08 08:24 UTC273
                    panic!("resync failed: {failure:?}")
96fdf90aacd6atagen2026-09-08 08:24 UTC274
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC275
                _ => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC276
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC277
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC278
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC279
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC280
    .expect("resync did not complete");
96fdf90aacd6atagen2026-09-08 08:24 UTC281
    let mut items = reset.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC282
    assert!(items.iter().any(|item| matches!(item,
96fdf90aacd6atagen2026-09-08 08:24 UTC283
        TimelineItem::Message(message) if message.body == "before reset"
96fdf90aacd6atagen2026-09-08 08:24 UTC284
    )));
96fdf90aacd6atagen2026-09-08 08:24 UTC285
96fdf90aacd6atagen2026-09-08 08:24 UTC286
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC287
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC288
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC289
            JoinedRoomBuilder::new(room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC290
                .add_timeline_event(EventFactory::new().text_msg("after reset").sender(&ALICE)),
96fdf90aacd6atagen2026-09-08 08:24 UTC291
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC292
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC293
    tokio::time::timeout(Duration::from_secs(2), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC294
        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC295
            if let Event::TimelineChanged { timeline_id, diffs } =
96fdf90aacd6atagen2026-09-08 08:24 UTC296
                events.recv().await.expect("event stream closed")
96fdf90aacd6atagen2026-09-08 08:24 UTC297
                && timeline_id.room_id() == room_id
96fdf90aacd6atagen2026-09-08 08:24 UTC298
            {
96fdf90aacd6atagen2026-09-08 08:24 UTC299
                for diff in diffs {
96fdf90aacd6atagen2026-09-08 08:24 UTC300
                    diff.try_apply(&mut items)
96fdf90aacd6atagen2026-09-08 08:24 UTC301
                        .expect("resynced snapshot accepts following diffs");
96fdf90aacd6atagen2026-09-08 08:24 UTC302
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC303
                if items.iter().any(|item| {
96fdf90aacd6atagen2026-09-08 08:24 UTC304
                    matches!(item,
96fdf90aacd6atagen2026-09-08 08:24 UTC305
                        TimelineItem::Message(message) if message.body == "after reset"
96fdf90aacd6atagen2026-09-08 08:24 UTC306
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC307
                }) {
96fdf90aacd6atagen2026-09-08 08:24 UTC308
                    break;
96fdf90aacd6atagen2026-09-08 08:24 UTC309
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC310
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC311
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC312
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC313
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC314
    .expect("resynced timeline did not update");
96fdf90aacd6atagen2026-09-08 08:24 UTC315
96fdf90aacd6atagen2026-09-08 08:24 UTC316
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC317
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
96fdf90aacd6atagen2026-09-08 08:24 UTC318
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC319
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC320
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC321
    running.await.unwrap().unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC322
}
96fdf90aacd6atagen2026-09-08 08:24 UTC323
96fdf90aacd6atagen2026-09-08 08:24 UTC324
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC325
async fn closes_and_reopens_a_room_in_command_order() {
96fdf90aacd6atagen2026-09-08 08:24 UTC326
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC327
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC328
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC329
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC330
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC331
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC332
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC333
    let room_id = room_id!("!reopen:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC334
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC335
    server.sync_joined_room(&client, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC336
    let config = EngineConfig::default().with_initial_history_limit(0);
96fdf90aacd6atagen2026-09-08 08:24 UTC337
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC338
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC339
96fdf90aacd6atagen2026-09-08 08:24 UTC340
    let initial_open = handle
96fdf90aacd6atagen2026-09-08 08:24 UTC341
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC342
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC343
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC344
    receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC345
        &mut events,
96fdf90aacd6atagen2026-09-08 08:24 UTC346
        |event| matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC347
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC348
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC349
    receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC350
        &mut events,
96fdf90aacd6atagen2026-09-08 08:24 UTC351
        |event| matches!(event, Event::CommandSucceeded { id, .. } if id == &initial_open),
96fdf90aacd6atagen2026-09-08 08:24 UTC352
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC353
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC354
96fdf90aacd6atagen2026-09-08 08:24 UTC355
    let close = handle
96fdf90aacd6atagen2026-09-08 08:24 UTC356
        .send(Command::Room(RoomCommand::Close(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC357
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC358
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC359
    let replacement_open = handle
96fdf90aacd6atagen2026-09-08 08:24 UTC360
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC361
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC362
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC363
    let second_close = handle
96fdf90aacd6atagen2026-09-08 08:24 UTC364
        .send(Command::Room(RoomCommand::Close(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC365
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC366
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC367
    let final_open = handle
96fdf90aacd6atagen2026-09-08 08:24 UTC368
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC369
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC370
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC371
    let expected = [close, replacement_open, second_close, final_open];
96fdf90aacd6atagen2026-09-08 08:24 UTC372
    let mut completed = 0;
96fdf90aacd6atagen2026-09-08 08:24 UTC373
    let mut resets = 0;
96fdf90aacd6atagen2026-09-08 08:24 UTC374
    tokio::time::timeout(Duration::from_secs(3), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC375
        while completed < expected.len() {
96fdf90aacd6atagen2026-09-08 08:24 UTC376
            match events.recv().await.expect("event stream closed") {
96fdf90aacd6atagen2026-09-08 08:24 UTC377
                Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id => {
96fdf90aacd6atagen2026-09-08 08:24 UTC378
                    resets += 1;
96fdf90aacd6atagen2026-09-08 08:24 UTC379
                    assert!(resets <= 2, "a room generation reset more than once");
96fdf90aacd6atagen2026-09-08 08:24 UTC380
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC381
                Event::CommandSucceeded { id, .. } if expected.contains(&id) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC382
                    assert_eq!(
96fdf90aacd6atagen2026-09-08 08:24 UTC383
                        id, expected[completed],
96fdf90aacd6atagen2026-09-08 08:24 UTC384
                        "room lifecycle commands completed out of order"
96fdf90aacd6atagen2026-09-08 08:24 UTC385
                    );
96fdf90aacd6atagen2026-09-08 08:24 UTC386
                    if completed == 1 || completed == 3 {
96fdf90aacd6atagen2026-09-08 08:24 UTC387
                        assert_eq!(
96fdf90aacd6atagen2026-09-08 08:24 UTC388
                            resets,
96fdf90aacd6atagen2026-09-08 08:24 UTC389
                            completed / 2 + 1,
96fdf90aacd6atagen2026-09-08 08:24 UTC390
                            "room open completed before timeline reset"
96fdf90aacd6atagen2026-09-08 08:24 UTC391
                        );
96fdf90aacd6atagen2026-09-08 08:24 UTC392
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC393
                    completed += 1;
96fdf90aacd6atagen2026-09-08 08:24 UTC394
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC395
                Event::CommandFailed(failure) if expected.contains(&failure.id) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC396
                    panic!("room lifecycle command failed: {failure:?}");
96fdf90aacd6atagen2026-09-08 08:24 UTC397
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC398
                _ => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC399
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC400
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC401
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC402
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC403
    .expect("room lifecycle commands did not complete");
96fdf90aacd6atagen2026-09-08 08:24 UTC404
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC405
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
96fdf90aacd6atagen2026-09-08 08:24 UTC406
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC407
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC408
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC409
    running.await.unwrap().unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC410
}
96fdf90aacd6atagen2026-09-08 08:24 UTC411
96fdf90aacd6atagen2026-09-08 08:24 UTC412
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC413
async fn thread_command_waits_for_opening_timeline() {
96fdf90aacd6atagen2026-09-08 08:24 UTC414
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC415
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC416
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC417
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC418
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC419
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC420
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC421
    let room_id = room_id!("!thread:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC422
    let root = event_id!("$thread-root:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC423
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC424
    server.sync_joined_room(&client, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC425
    let (engine, handle, mut events) = Engine::from_client(
96fdf90aacd6atagen2026-09-08 08:24 UTC426
        client,
96fdf90aacd6atagen2026-09-08 08:24 UTC427
        EngineConfig::default().with_initial_history_limit(0),
96fdf90aacd6atagen2026-09-08 08:24 UTC428
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC429
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC430
    .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC431
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC432
    let room_open = handle
96fdf90aacd6atagen2026-09-08 08:24 UTC433
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC434
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC435
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC436
    receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC437
        &mut events,
96fdf90aacd6atagen2026-09-08 08:24 UTC438
        |event| matches!(event, Event::CommandSucceeded { id, .. } if *id == room_open),
96fdf90aacd6atagen2026-09-08 08:24 UTC439
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC440
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC441
96fdf90aacd6atagen2026-09-08 08:24 UTC442
    let open = handle
96fdf90aacd6atagen2026-09-08 08:24 UTC443
        .send(Command::Timeline(TimelineCommand::OpenThread {
96fdf90aacd6atagen2026-09-08 08:24 UTC444
            room_id: room_id.to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC445
            root_event_id: root.to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC446
        }))
96fdf90aacd6atagen2026-09-08 08:24 UTC447
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC448
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC449
    let send = handle
96fdf90aacd6atagen2026-09-08 08:24 UTC450
        .send(Command::Timeline(TimelineCommand::Send {
96fdf90aacd6atagen2026-09-08 08:24 UTC451
            timeline_id: TimelineId::thread(room_id.to_owned(), root.to_owned()),
96fdf90aacd6atagen2026-09-08 08:24 UTC452
            content: MessageContent::text(""),
96fdf90aacd6atagen2026-09-08 08:24 UTC453
        }))
96fdf90aacd6atagen2026-09-08 08:24 UTC454
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC455
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC456
    let resync = handle
96fdf90aacd6atagen2026-09-08 08:24 UTC457
        .send(Command::Timeline(TimelineCommand::Resync {
96fdf90aacd6atagen2026-09-08 08:24 UTC458
            timeline_id: TimelineId::thread(room_id.to_owned(), root.to_owned()),
96fdf90aacd6atagen2026-09-08 08:24 UTC459
        }))
96fdf90aacd6atagen2026-09-08 08:24 UTC460
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC461
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC462
    let mut open_succeeded = false;
96fdf90aacd6atagen2026-09-08 08:24 UTC463
    tokio::time::timeout(Duration::from_secs(3), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC464
        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC465
            match events.recv().await.expect("event stream closed") {
96fdf90aacd6atagen2026-09-08 08:24 UTC466
                Event::CommandSucceeded { id, .. } if id == open => open_succeeded = true,
96fdf90aacd6atagen2026-09-08 08:24 UTC467
                Event::CommandFailed(failure) if failure.id == send => {
96fdf90aacd6atagen2026-09-08 08:24 UTC468
                    assert!(open_succeeded, "thread command completed before opening");
96fdf90aacd6atagen2026-09-08 08:24 UTC469
                    assert_eq!(failure.kind, CommandErrorKind::EmptyBody);
96fdf90aacd6atagen2026-09-08 08:24 UTC470
                    break;
96fdf90aacd6atagen2026-09-08 08:24 UTC471
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC472
                Event::CommandFailed(failure) if failure.id == open => {
96fdf90aacd6atagen2026-09-08 08:24 UTC473
                    panic!("thread open failed: {failure:?}");
96fdf90aacd6atagen2026-09-08 08:24 UTC474
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC475
                _ => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC476
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC477
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC478
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC479
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC480
    .expect("thread commands did not complete");
96fdf90aacd6atagen2026-09-08 08:24 UTC481
    let mut reset_seen = false;
96fdf90aacd6atagen2026-09-08 08:24 UTC482
    tokio::time::timeout(Duration::from_secs(3), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC483
        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC484
            match events.recv().await.expect("event stream closed") {
96fdf90aacd6atagen2026-09-08 08:24 UTC485
                Event::TimelineReset { timeline_id, .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC486
                    if timeline_id.thread_root().is_some_and(|event| event == root) =>
96fdf90aacd6atagen2026-09-08 08:24 UTC487
                {
96fdf90aacd6atagen2026-09-08 08:24 UTC488
                    reset_seen = true
96fdf90aacd6atagen2026-09-08 08:24 UTC489
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC490
                Event::CommandSucceeded { id, .. } if id == resync => {
96fdf90aacd6atagen2026-09-08 08:24 UTC491
                    assert!(reset_seen, "thread resync completed before its reset");
96fdf90aacd6atagen2026-09-08 08:24 UTC492
                    break;
96fdf90aacd6atagen2026-09-08 08:24 UTC493
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC494
                Event::CommandFailed(failure) if failure.id == resync => {
96fdf90aacd6atagen2026-09-08 08:24 UTC495
                    panic!("thread resync failed: {failure:?}")
96fdf90aacd6atagen2026-09-08 08:24 UTC496
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC497
                _ => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC498
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC499
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC500
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC501
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC502
    .expect("thread resync did not complete");
96fdf90aacd6atagen2026-09-08 08:24 UTC503
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC504
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
96fdf90aacd6atagen2026-09-08 08:24 UTC505
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC506
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC507
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC508
    running.await.unwrap().unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC509
}
96fdf90aacd6atagen2026-09-08 08:24 UTC510
96fdf90aacd6atagen2026-09-08 08:24 UTC511
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC512
async fn pagination_reports_and_remembers_start_of_history() {
96fdf90aacd6atagen2026-09-08 08:24 UTC513
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC514
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC515
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC516
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC517
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC518
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC519
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC520
    let sync_client = client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC521
    let room_id = room_id!("!history:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC522
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC523
    server.sync_joined_room(&client, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC524
    let config = EngineConfig::default().with_initial_history_limit(0);
96fdf90aacd6atagen2026-09-08 08:24 UTC525
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC526
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC527
96fdf90aacd6atagen2026-09-08 08:24 UTC528
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC529
        .send(Command::Room(RoomCommand::Open(room_id.to_owned())))
96fdf90aacd6atagen2026-09-08 08:24 UTC530
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC531
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC532
    receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC533
        &mut events,
96fdf90aacd6atagen2026-09-08 08:24 UTC534
        |event| matches!(event, Event::TimelineReset { timeline_id: reset, .. } if reset.room_id() == room_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC535
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC536
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC537
96fdf90aacd6atagen2026-09-08 08:24 UTC538
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC539
        .sync_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC540
            &sync_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC541
            JoinedRoomBuilder::new(room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC542
                .add_timeline_event(EventFactory::new().text_msg("latest").sender(&ALICE))
96fdf90aacd6atagen2026-09-08 08:24 UTC543
                .set_timeline_limited()
96fdf90aacd6atagen2026-09-08 08:24 UTC544
                .set_timeline_prev_batch("before-latest"),
96fdf90aacd6atagen2026-09-08 08:24 UTC545
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC546
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC547
    receive_until(&mut events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC548
        matches!(event, Event::TimelineChanged { timeline_id: changed, .. } if changed.room_id() == room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC549
            && event_messages(event).any(|message| message.body == "latest")
96fdf90aacd6atagen2026-09-08 08:24 UTC550
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC551
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC552
96fdf90aacd6atagen2026-09-08 08:24 UTC553
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC554
        .mock_room_messages()
96fdf90aacd6atagen2026-09-08 08:24 UTC555
        .match_from("before-latest")
96fdf90aacd6atagen2026-09-08 08:24 UTC556
        .ok(RoomMessagesResponseTemplate::default())
96fdf90aacd6atagen2026-09-08 08:24 UTC557
        .mock_once()
96fdf90aacd6atagen2026-09-08 08:24 UTC558
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC559
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC560
    let mut reached_start = false;
96fdf90aacd6atagen2026-09-08 08:24 UTC561
    for _ in 0..3 {
96fdf90aacd6atagen2026-09-08 08:24 UTC562
        handle
96fdf90aacd6atagen2026-09-08 08:24 UTC563
            .send(Command::Timeline(TimelineCommand::Paginate {
96fdf90aacd6atagen2026-09-08 08:24 UTC564
                timeline_id: room_id.to_owned().into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC565
                limit: 10,
96fdf90aacd6atagen2026-09-08 08:24 UTC566
            }))
96fdf90aacd6atagen2026-09-08 08:24 UTC567
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC568
            .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC569
        receive_history(&mut events, room_id, HistoryState::Loading).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC570
        match receive_history_terminal(&mut events, room_id).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC571
            HistoryState::StartReached => {
96fdf90aacd6atagen2026-09-08 08:24 UTC572
                reached_start = true;
96fdf90aacd6atagen2026-09-08 08:24 UTC573
                break;
96fdf90aacd6atagen2026-09-08 08:24 UTC574
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC575
            HistoryState::Idle => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC576
            state => panic!("unexpected pagination state {state:?}"),
96fdf90aacd6atagen2026-09-08 08:24 UTC577
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC578
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC579
    assert!(
96fdf90aacd6atagen2026-09-08 08:24 UTC580
        reached_start,
96fdf90aacd6atagen2026-09-08 08:24 UTC581
        "pagination did not reach the mocked room start"
96fdf90aacd6atagen2026-09-08 08:24 UTC582
    );
96fdf90aacd6atagen2026-09-08 08:24 UTC583
96fdf90aacd6atagen2026-09-08 08:24 UTC584
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC585
        .send(Command::Timeline(TimelineCommand::Paginate {
96fdf90aacd6atagen2026-09-08 08:24 UTC586
            timeline_id: room_id.to_owned().into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC587
            limit: 10,
96fdf90aacd6atagen2026-09-08 08:24 UTC588
        }))
96fdf90aacd6atagen2026-09-08 08:24 UTC589
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC590
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC591
    receive_history(&mut events, room_id, HistoryState::StartReached).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC592
96fdf90aacd6atagen2026-09-08 08:24 UTC593
    handle
96fdf90aacd6atagen2026-09-08 08:24 UTC594
        .send(Command::Lifecycle(LifecycleCommand::Shutdown))
96fdf90aacd6atagen2026-09-08 08:24 UTC595
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC596
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC597
    receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC598
    running.await.unwrap().unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC599
}
96fdf90aacd6atagen2026-09-08 08:24 UTC600
96fdf90aacd6atagen2026-09-08 08:24 UTC601
async fn receive_history(
96fdf90aacd6atagen2026-09-08 08:24 UTC602
    events: &mut macltok::EventStream,
96fdf90aacd6atagen2026-09-08 08:24 UTC603
    room_id: &matrix_sdk::ruma::RoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC604
    expected: HistoryState,
96fdf90aacd6atagen2026-09-08 08:24 UTC605
) {
96fdf90aacd6atagen2026-09-08 08:24 UTC606
    receive_until(events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC607
        matches!(event, Event::HistoryChanged { timeline_id: changed, state } if changed.room_id() == room_id && state == &expected)
96fdf90aacd6atagen2026-09-08 08:24 UTC608
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC609
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC610
}
96fdf90aacd6atagen2026-09-08 08:24 UTC611
96fdf90aacd6atagen2026-09-08 08:24 UTC612
fn event_messages(event: &Event) -> std::vec::IntoIter<&macltok::TimelineMessage> {
96fdf90aacd6atagen2026-09-08 08:24 UTC613
    let items = match event {
96fdf90aacd6atagen2026-09-08 08:24 UTC614
        Event::TimelineReset { items, .. } => items.iter().collect::<Vec<_>>(),
96fdf90aacd6atagen2026-09-08 08:24 UTC615
        Event::TimelineChanged { diffs, .. } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC616
            diffs.iter().flat_map(diff_items).collect::<Vec<_>>()
96fdf90aacd6atagen2026-09-08 08:24 UTC617
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC618
        _ => Vec::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC619
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC620
    items
96fdf90aacd6atagen2026-09-08 08:24 UTC621
        .into_iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC622
        .filter_map(|item| match item {
96fdf90aacd6atagen2026-09-08 08:24 UTC623
            TimelineItem::Message(message) => Some(message.as_ref()),
96fdf90aacd6atagen2026-09-08 08:24 UTC624
            _ => None,
96fdf90aacd6atagen2026-09-08 08:24 UTC625
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC626
        .collect::<Vec<_>>()
96fdf90aacd6atagen2026-09-08 08:24 UTC627
        .into_iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC628
}
96fdf90aacd6atagen2026-09-08 08:24 UTC629
96fdf90aacd6atagen2026-09-08 08:24 UTC630
fn diff_items(diff: &TimelineDiff) -> Vec<&TimelineItem> {
96fdf90aacd6atagen2026-09-08 08:24 UTC631
    match diff {
96fdf90aacd6atagen2026-09-08 08:24 UTC632
        TimelineDiff::Append { values } | TimelineDiff::Reset { values } => values.iter().collect(),
96fdf90aacd6atagen2026-09-08 08:24 UTC633
        TimelineDiff::PushFront { value }
96fdf90aacd6atagen2026-09-08 08:24 UTC634
        | TimelineDiff::PushBack { value }
96fdf90aacd6atagen2026-09-08 08:24 UTC635
        | TimelineDiff::Insert { value, .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC636
        | TimelineDiff::Set { value, .. } => vec![value],
96fdf90aacd6atagen2026-09-08 08:24 UTC637
        TimelineDiff::Clear
96fdf90aacd6atagen2026-09-08 08:24 UTC638
        | TimelineDiff::PopFront
96fdf90aacd6atagen2026-09-08 08:24 UTC639
        | TimelineDiff::PopBack
96fdf90aacd6atagen2026-09-08 08:24 UTC640
        | TimelineDiff::Remove { .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC641
        | TimelineDiff::Truncate { .. } => Vec::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC642
        _ => Vec::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC643
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC644
}
96fdf90aacd6atagen2026-09-08 08:24 UTC645
96fdf90aacd6atagen2026-09-08 08:24 UTC646
async fn receive_history_terminal(
96fdf90aacd6atagen2026-09-08 08:24 UTC647
    events: &mut macltok::EventStream,
96fdf90aacd6atagen2026-09-08 08:24 UTC648
    room_id: &matrix_sdk::ruma::RoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC649
) -> HistoryState {
96fdf90aacd6atagen2026-09-08 08:24 UTC650
    match receive_until(events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC651
        matches!(event, Event::HistoryChanged { timeline_id: changed, state } if changed.room_id() == room_id && !matches!(state, HistoryState::Loading))
96fdf90aacd6atagen2026-09-08 08:24 UTC652
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC653
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC654
    {
96fdf90aacd6atagen2026-09-08 08:24 UTC655
        Event::HistoryChanged { state, .. } => state,
96fdf90aacd6atagen2026-09-08 08:24 UTC656
        _ => unreachable!(),
96fdf90aacd6atagen2026-09-08 08:24 UTC657
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC658
}
96fdf90aacd6atagen2026-09-08 08:24 UTC659
96fdf90aacd6atagen2026-09-08 08:24 UTC660
async fn wait_for_room_subscriptions(
96fdf90aacd6atagen2026-09-08 08:24 UTC661
    server: &MatrixMockServer,
96fdf90aacd6atagen2026-09-08 08:24 UTC662
    room_ids: &[&matrix_sdk::ruma::RoomId],
96fdf90aacd6atagen2026-09-08 08:24 UTC663
) {
96fdf90aacd6atagen2026-09-08 08:24 UTC664
    tokio::time::timeout(Duration::from_secs(2), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC665
        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC666
            let found = server
96fdf90aacd6atagen2026-09-08 08:24 UTC667
                .received_requests()
96fdf90aacd6atagen2026-09-08 08:24 UTC668
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC669
                .unwrap()
96fdf90aacd6atagen2026-09-08 08:24 UTC670
                .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC671
                .filter_map(|request| {
96fdf90aacd6atagen2026-09-08 08:24 UTC672
                    serde_json::from_slice::<serde_json::Value>(&request.body).ok()
96fdf90aacd6atagen2026-09-08 08:24 UTC673
                })
96fdf90aacd6atagen2026-09-08 08:24 UTC674
                .filter_map(|body| {
96fdf90aacd6atagen2026-09-08 08:24 UTC675
                    body.get("room_subscriptions")
96fdf90aacd6atagen2026-09-08 08:24 UTC676
                        .and_then(|value| value.as_object())
96fdf90aacd6atagen2026-09-08 08:24 UTC677
                        .cloned()
96fdf90aacd6atagen2026-09-08 08:24 UTC678
                })
96fdf90aacd6atagen2026-09-08 08:24 UTC679
                .any(|subscriptions| {
96fdf90aacd6atagen2026-09-08 08:24 UTC680
                    room_ids
96fdf90aacd6atagen2026-09-08 08:24 UTC681
                        .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC682
                        .all(|room_id| subscriptions.contains_key(room_id.as_str()))
96fdf90aacd6atagen2026-09-08 08:24 UTC683
                });
96fdf90aacd6atagen2026-09-08 08:24 UTC684
            if found {
96fdf90aacd6atagen2026-09-08 08:24 UTC685
                break;
96fdf90aacd6atagen2026-09-08 08:24 UTC686
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC687
            tokio::task::yield_now().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC688
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC689
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC690
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC691
    .expect("Sliding Sync did not retain every open room subscription");
96fdf90aacd6atagen2026-09-08 08:24 UTC692
}
96fdf90aacd6atagen2026-09-08 08:24 UTC693
96fdf90aacd6atagen2026-09-08 08:24 UTC694
async fn receive_until(
96fdf90aacd6atagen2026-09-08 08:24 UTC695
    events: &mut macltok::EventStream,
96fdf90aacd6atagen2026-09-08 08:24 UTC696
    predicate: impl Fn(&Event) -> bool,
96fdf90aacd6atagen2026-09-08 08:24 UTC697
) -> Event {
96fdf90aacd6atagen2026-09-08 08:24 UTC698
    tokio::time::timeout(Duration::from_secs(2), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC699
        while let Some(event) = events.recv().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC700
            if predicate(&event) {
96fdf90aacd6atagen2026-09-08 08:24 UTC701
                return event;
96fdf90aacd6atagen2026-09-08 08:24 UTC702
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC703
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC704
        panic!("event stream closed before the expected event");
96fdf90aacd6atagen2026-09-08 08:24 UTC705
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC706
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC707
    .expect("expected event did not arrive")
96fdf90aacd6atagen2026-09-08 08:24 UTC708
}