atagen

macltok

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

Blame: tests/state.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
fe4133dbcd47atagen2026-09-26 09:05 UTC5
use macltok::{
17e042fbc331atagen2026-10-08 12:39 UTC6
    ConnectionState, Engine, EngineConfig, EngineError, Event, PackUsage, Retain, RoomMembership,
e5c6427adb35atagen2026-10-25 10:23 UTC7
    Termination, ThreadUnread,
fe4133dbcd47atagen2026-09-26 09:05 UTC8
};
96fdf90aacd6atagen2026-09-08 08:24 UTC9
use matrix_sdk::{
e5c6427adb35atagen2026-10-25 10:23 UTC10
    ruma::{
e5c6427adb35atagen2026-10-25 10:23 UTC11
        RoomId,
e5c6427adb35atagen2026-10-25 10:23 UTC12
        api::client::sync::sync_events::v5,
e5c6427adb35atagen2026-10-25 10:23 UTC13
        event_id,
e5c6427adb35atagen2026-10-25 10:23 UTC14
        events::{
e5c6427adb35atagen2026-10-25 10:23 UTC15
            AnySyncStateEvent, Mentions,
e5c6427adb35atagen2026-10-25 10:23 UTC16
            receipt::{ReceiptThread, ReceiptType},
e5c6427adb35atagen2026-10-25 10:23 UTC17
        },
e5c6427adb35atagen2026-10-25 10:23 UTC18
        room_id,
e5c6427adb35atagen2026-10-25 10:23 UTC19
        serde::Raw,
e5c6427adb35atagen2026-10-25 10:23 UTC20
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC21
    test_utils::mocks::MatrixMockServer,
96fdf90aacd6atagen2026-09-08 08:24 UTC22
};
e5c6427adb35atagen2026-10-25 10:23 UTC23
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, event_factory::EventFactory};
17e042fbc331atagen2026-10-08 12:39 UTC24
use serde_json::json;
96fdf90aacd6atagen2026-09-08 08:24 UTC25
use tokio::sync::watch;
96fdf90aacd6atagen2026-09-08 08:24 UTC26
96fdf90aacd6atagen2026-09-08 08:24 UTC27
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC28
async fn rooms_changed_invalidates_room_summaries() {
96fdf90aacd6atagen2026-09-08 08:24 UTC29
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC30
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC31
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC32
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC33
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC34
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC35
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC36
    let room_id = room_id!("!state:localhost");
96fdf90aacd6atagen2026-09-08 08:24 UTC37
    server.mock_room_state_encryption().plain().mount().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC38
    server.sync_joined_room(&client, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC39
    let config = EngineConfig::default().with_initial_history_limit(0);
96fdf90aacd6atagen2026-09-08 08:24 UTC40
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC41
    let running = tokio::spawn(engine.run());
96fdf90aacd6atagen2026-09-08 08:24 UTC42
31ce2453c82fatagen2026-09-24 13:07 UTC43
    support::receive_until(&mut events, |event| matches!(event, Event::RoomsChanged)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC44
    let rooms = handle.rooms().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC45
    let summary = rooms
96fdf90aacd6atagen2026-09-08 08:24 UTC46
        .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC47
        .find(|summary| summary.room_id == room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC48
        .expect("synced room was absent from the room summaries");
96fdf90aacd6atagen2026-09-08 08:24 UTC49
    assert_eq!(summary.membership, RoomMembership::Joined);
96fdf90aacd6atagen2026-09-08 08:24 UTC50
    assert!(!summary.name.is_empty());
96fdf90aacd6atagen2026-09-08 08:24 UTC51
31ce2453c82fatagen2026-09-24 13:07 UTC52
    support::shut_down(&handle, &mut events, running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC53
}
96fdf90aacd6atagen2026-09-08 08:24 UTC54
96fdf90aacd6atagen2026-09-08 08:24 UTC55
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC56
async fn connection_watch_tracks_the_engine_lifecycle() {
96fdf90aacd6atagen2026-09-08 08:24 UTC57
    let server = MatrixMockServer::new().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC58
    server
96fdf90aacd6atagen2026-09-08 08:24 UTC59
        .mock_sliding_sync()
96fdf90aacd6atagen2026-09-08 08:24 UTC60
        .ok(v5::Response::new("1".to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC61
        .mount()
96fdf90aacd6atagen2026-09-08 08:24 UTC62
        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC63
    let client = server.client_builder().build().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC64
    let config = EngineConfig::default().with_initial_history_limit(0);
96fdf90aacd6atagen2026-09-08 08:24 UTC65
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC66
    let mut connection = handle.connection_state();
96fdf90aacd6atagen2026-09-08 08:24 UTC67
    let connection_peer = connection.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC68
    assert_eq!(*connection.borrow(), ConnectionState::Idle);
96fdf90aacd6atagen2026-09-08 08:24 UTC69
    assert_eq!(*connection_peer.borrow(), ConnectionState::Idle);
96fdf90aacd6atagen2026-09-08 08:24 UTC70
96fdf90aacd6atagen2026-09-08 08:24 UTC71
    let running = tokio::spawn(engine.run());
31ce2453c82fatagen2026-09-24 13:07 UTC72
    support::receive_until(&mut events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC73
        matches!(event, Event::ConnectionChanged(ConnectionState::Connecting))
96fdf90aacd6atagen2026-09-08 08:24 UTC74
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC75
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC76
    assert_ne!(*connection.borrow(), ConnectionState::Idle);
31ce2453c82fatagen2026-09-24 13:07 UTC77
    support::receive_until(&mut events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC78
        matches!(event, Event::ConnectionChanged(ConnectionState::Online))
96fdf90aacd6atagen2026-09-08 08:24 UTC79
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC80
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC81
    wait_for_connection(&mut connection, ConnectionState::Online).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC82
    assert_eq!(*connection_peer.borrow(), ConnectionState::Online);
96fdf90aacd6atagen2026-09-08 08:24 UTC83
31ce2453c82fatagen2026-09-24 13:07 UTC84
    support::shut_down(&handle, &mut events, running).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC85
    wait_for_connection(&mut connection, ConnectionState::Stopped).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC86
    assert_eq!(*connection_peer.borrow(), ConnectionState::Stopped);
96fdf90aacd6atagen2026-09-08 08:24 UTC87
}
96fdf90aacd6atagen2026-09-08 08:24 UTC88
96fdf90aacd6atagen2026-09-08 08:24 UTC89
#[tokio::test]
fa9114fcf75fatagen2026-09-24 12:51 UTC90
async fn soft_logout_says_so_and_stops_the_engine() {
fa9114fcf75fatagen2026-09-24 12:51 UTC91
    let server = MatrixMockServer::new().await;
fa9114fcf75fatagen2026-09-24 12:51 UTC92
    server
fa9114fcf75fatagen2026-09-24 12:51 UTC93
        .mock_sliding_sync()
fa9114fcf75fatagen2026-09-24 12:51 UTC94
        .error_unknown_token(true)
fa9114fcf75fatagen2026-09-24 12:51 UTC95
        .mount()
fa9114fcf75fatagen2026-09-24 12:51 UTC96
        .await;
fa9114fcf75fatagen2026-09-24 12:51 UTC97
    let client = server.client_builder().build().await;
fa9114fcf75fatagen2026-09-24 12:51 UTC98
    let (engine, _handle, mut events) = Engine::from_client(
fa9114fcf75fatagen2026-09-24 12:51 UTC99
        client,
fa9114fcf75fatagen2026-09-24 12:51 UTC100
        EngineConfig::default().with_initial_history_limit(0),
fa9114fcf75fatagen2026-09-24 12:51 UTC101
    )
fa9114fcf75fatagen2026-09-24 12:51 UTC102
    .await
fa9114fcf75fatagen2026-09-24 12:51 UTC103
    .unwrap();
fa9114fcf75fatagen2026-09-24 12:51 UTC104
    let running = tokio::spawn(engine.run());
31ce2453c82fatagen2026-09-24 13:07 UTC105
    support::receive_until(&mut events, |event| {
7c26fad34822atagen2026-09-26 09:41 UTC106
        matches!(
7c26fad34822atagen2026-09-26 09:41 UTC107
            event,
7c26fad34822atagen2026-09-26 09:41 UTC108
            Event::SessionAccessRejected {
7c26fad34822atagen2026-09-26 09:41 UTC109
                soft_logout: Some(_)
7c26fad34822atagen2026-09-26 09:41 UTC110
            }
7c26fad34822atagen2026-09-26 09:41 UTC111
        )
fa9114fcf75fatagen2026-09-24 12:51 UTC112
    })
fa9114fcf75fatagen2026-09-24 12:51 UTC113
    .await;
31ce2453c82fatagen2026-09-24 13:07 UTC114
    support::receive_until(&mut events, |event| matches!(event, Event::Stopped)).await;
fe4133dbcd47atagen2026-09-26 09:05 UTC115
    let termination = running.await.unwrap().unwrap();
fe4133dbcd47atagen2026-09-26 09:05 UTC116
    assert!(matches!(termination, Termination::SoftLoggedOut { .. }));
fe4133dbcd47atagen2026-09-26 09:05 UTC117
    assert_eq!(termination.retain(), Retain::Store);
fa9114fcf75fatagen2026-09-24 12:51 UTC118
}
fa9114fcf75fatagen2026-09-24 12:51 UTC119
fa9114fcf75fatagen2026-09-24 12:51 UTC120
#[tokio::test]
96fdf90aacd6atagen2026-09-08 08:24 UTC121
async fn closing_the_event_stream_stops_an_idle_engine() {
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 (engine, handle, mut events) = Engine::from_client(
96fdf90aacd6atagen2026-09-08 08:24 UTC130
        client,
96fdf90aacd6atagen2026-09-08 08:24 UTC131
        EngineConfig::default().with_initial_history_limit(0),
96fdf90aacd6atagen2026-09-08 08:24 UTC132
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC133
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC134
    .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC135
    let running = tokio::spawn(engine.run());
31ce2453c82fatagen2026-09-24 13:07 UTC136
    support::receive_until(&mut events, |event| {
96fdf90aacd6atagen2026-09-08 08:24 UTC137
        matches!(event, Event::ConnectionChanged(ConnectionState::Online))
96fdf90aacd6atagen2026-09-08 08:24 UTC138
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC139
    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC140
    drop(events);
96fdf90aacd6atagen2026-09-08 08:24 UTC141
    let result = tokio::time::timeout(Duration::from_secs(2), running)
96fdf90aacd6atagen2026-09-08 08:24 UTC142
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC143
        .expect("engine did not stop after its event receiver closed")
96fdf90aacd6atagen2026-09-08 08:24 UTC144
        .unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC145
    assert!(matches!(result, Err(EngineError::EventReceiverClosed)));
96fdf90aacd6atagen2026-09-08 08:24 UTC146
    drop(handle);
96fdf90aacd6atagen2026-09-08 08:24 UTC147
}
96fdf90aacd6atagen2026-09-08 08:24 UTC148
96fdf90aacd6atagen2026-09-08 08:24 UTC149
async fn wait_for_connection(
96fdf90aacd6atagen2026-09-08 08:24 UTC150
    connection: &mut watch::Receiver<ConnectionState>,
96fdf90aacd6atagen2026-09-08 08:24 UTC151
    expected: ConnectionState,
96fdf90aacd6atagen2026-09-08 08:24 UTC152
) {
96fdf90aacd6atagen2026-09-08 08:24 UTC153
    tokio::time::timeout(Duration::from_secs(2), async {
96fdf90aacd6atagen2026-09-08 08:24 UTC154
        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC155
            if *connection.borrow_and_update() == expected {
96fdf90aacd6atagen2026-09-08 08:24 UTC156
                return;
96fdf90aacd6atagen2026-09-08 08:24 UTC157
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC158
            connection
96fdf90aacd6atagen2026-09-08 08:24 UTC159
                .changed()
96fdf90aacd6atagen2026-09-08 08:24 UTC160
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC161
                .expect("connection watch closed before the expected state");
96fdf90aacd6atagen2026-09-08 08:24 UTC162
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC163
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC164
    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC165
    .expect("expected connection state did not arrive");
96fdf90aacd6atagen2026-09-08 08:24 UTC166
}
17e042fbc331atagen2026-10-08 12:39 UTC167
17e042fbc331atagen2026-10-08 12:39 UTC168
#[tokio::test]
17e042fbc331atagen2026-10-08 12:39 UTC169
async fn image_packs_follow_room_state_and_account_data() {
17e042fbc331atagen2026-10-08 12:39 UTC170
    let server = MatrixMockServer::new().await;
17e042fbc331atagen2026-10-08 12:39 UTC171
    server
17e042fbc331atagen2026-10-08 12:39 UTC172
        .mock_sliding_sync()
17e042fbc331atagen2026-10-08 12:39 UTC173
        .ok(v5::Response::new("1".to_owned()))
17e042fbc331atagen2026-10-08 12:39 UTC174
        .mount()
17e042fbc331atagen2026-10-08 12:39 UTC175
        .await;
17e042fbc331atagen2026-10-08 12:39 UTC176
    let client = server.client_builder().build().await;
17e042fbc331atagen2026-10-08 12:39 UTC177
    let room_id = room_id!("!packs:localhost");
17e042fbc331atagen2026-10-08 12:39 UTC178
    server.mock_room_state_encryption().plain().mount().await;
17e042fbc331atagen2026-10-08 12:39 UTC179
    server.sync_joined_room(&client, room_id).await;
17e042fbc331atagen2026-10-08 12:39 UTC180
    let config = EngineConfig::default().with_initial_history_limit(0);
17e042fbc331atagen2026-10-08 12:39 UTC181
    let (engine, handle, mut events) = Engine::from_client(client.clone(), config).await.unwrap();
17e042fbc331atagen2026-10-08 12:39 UTC182
    let running = tokio::spawn(engine.run());
17e042fbc331atagen2026-10-08 12:39 UTC183
17e042fbc331atagen2026-10-08 12:39 UTC184
    let pack: Raw<AnySyncStateEvent> = serde_json::from_value(json!({
17e042fbc331atagen2026-10-08 12:39 UTC185
        "type": "im.ponies.room_emotes",
17e042fbc331atagen2026-10-08 12:39 UTC186
        "state_key": "cats",
17e042fbc331atagen2026-10-08 12:39 UTC187
        "event_id": "$pack",
17e042fbc331atagen2026-10-08 12:39 UTC188
        "sender": "@example:localhost",
17e042fbc331atagen2026-10-08 12:39 UTC189
        "origin_server_ts": 1,
17e042fbc331atagen2026-10-08 12:39 UTC190
        "content": {
17e042fbc331atagen2026-10-08 12:39 UTC191
            "pack": { "display_name": "cats", "usage": ["emoticon"] },
17e042fbc331atagen2026-10-08 12:39 UTC192
            "images": {
17e042fbc331atagen2026-10-08 12:39 UTC193
                "blep": { "url": "mxc://localhost/blep" },
17e042fbc331atagen2026-10-08 12:39 UTC194
                "broken": { "url": "not an mxc" },
17e042fbc331atagen2026-10-08 12:39 UTC195
                "sticker": { "url": "mxc://localhost/sticker", "usage": ["sticker"] },
17e042fbc331atagen2026-10-08 12:39 UTC196
            },
17e042fbc331atagen2026-10-08 12:39 UTC197
        },
17e042fbc331atagen2026-10-08 12:39 UTC198
    }))
17e042fbc331atagen2026-10-08 12:39 UTC199
    .unwrap();
17e042fbc331atagen2026-10-08 12:39 UTC200
    server
17e042fbc331atagen2026-10-08 12:39 UTC201
        .mock_sync()
17e042fbc331atagen2026-10-08 12:39 UTC202
        .ok_and_run(&client, |builder| {
17e042fbc331atagen2026-10-08 12:39 UTC203
            builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_state_event(pack));
17e042fbc331atagen2026-10-08 12:39 UTC204
            builder.add_custom_global_account_data(json!({
17e042fbc331atagen2026-10-08 12:39 UTC205
                "type": "im.ponies.emote_rooms",
17e042fbc331atagen2026-10-08 12:39 UTC206
                "content": { "rooms": { "!packs:localhost": { "cats": {} } } },
17e042fbc331atagen2026-10-08 12:39 UTC207
            }));
17e042fbc331atagen2026-10-08 12:39 UTC208
        })
17e042fbc331atagen2026-10-08 12:39 UTC209
        .await;
17e042fbc331atagen2026-10-08 12:39 UTC210
    support::receive_until(&mut events, |event| {
17e042fbc331atagen2026-10-08 12:39 UTC211
        matches!(event, Event::ImagePacksChanged)
17e042fbc331atagen2026-10-08 12:39 UTC212
    })
17e042fbc331atagen2026-10-08 12:39 UTC213
    .await;
17e042fbc331atagen2026-10-08 12:39 UTC214
17e042fbc331atagen2026-10-08 12:39 UTC215
    let packs = handle.image_packs().await.unwrap();
17e042fbc331atagen2026-10-08 12:39 UTC216
    let offered = packs.for_room(Some(room_id));
17e042fbc331atagen2026-10-08 12:39 UTC217
    assert_eq!(offered.len(), 1, "an enabled room's pack was offered twice");
17e042fbc331atagen2026-10-08 12:39 UTC218
    let pack = offered[0];
17e042fbc331atagen2026-10-08 12:39 UTC219
    assert_eq!(pack.id, "room:!packs:localhost:cats");
17e042fbc331atagen2026-10-08 12:39 UTC220
    assert_eq!(packs.global, vec![pack.clone()]);
17e042fbc331atagen2026-10-08 12:39 UTC221
    let images: Vec<_> = pack
17e042fbc331atagen2026-10-08 12:39 UTC222
        .images
17e042fbc331atagen2026-10-08 12:39 UTC223
        .iter()
17e042fbc331atagen2026-10-08 12:39 UTC224
        .map(|image| {
17e042fbc331atagen2026-10-08 12:39 UTC225
            (
17e042fbc331atagen2026-10-08 12:39 UTC226
                image.shortcode.as_str(),
17e042fbc331atagen2026-10-08 12:39 UTC227
                image.reaction_key.as_str(),
17e042fbc331atagen2026-10-08 12:39 UTC228
                image.usage.clone(),
17e042fbc331atagen2026-10-08 12:39 UTC229
            )
17e042fbc331atagen2026-10-08 12:39 UTC230
        })
17e042fbc331atagen2026-10-08 12:39 UTC231
        .collect();
17e042fbc331atagen2026-10-08 12:39 UTC232
    assert_eq!(
17e042fbc331atagen2026-10-08 12:39 UTC233
        images,
17e042fbc331atagen2026-10-08 12:39 UTC234
        [
17e042fbc331atagen2026-10-08 12:39 UTC235
            ("blep", "mxc://localhost/blep", vec![PackUsage::Emoticon]),
17e042fbc331atagen2026-10-08 12:39 UTC236
            (
17e042fbc331atagen2026-10-08 12:39 UTC237
                "sticker",
17e042fbc331atagen2026-10-08 12:39 UTC238
                "mxc://localhost/sticker",
17e042fbc331atagen2026-10-08 12:39 UTC239
                vec![PackUsage::Sticker]
17e042fbc331atagen2026-10-08 12:39 UTC240
            ),
17e042fbc331atagen2026-10-08 12:39 UTC241
        ]
17e042fbc331atagen2026-10-08 12:39 UTC242
    );
17e042fbc331atagen2026-10-08 12:39 UTC243
17e042fbc331atagen2026-10-08 12:39 UTC244
    support::shut_down(&handle, &mut events, running).await;
17e042fbc331atagen2026-10-08 12:39 UTC245
}
e5c6427adb35atagen2026-10-25 10:23 UTC246
e5c6427adb35atagen2026-10-25 10:23 UTC247
/// the sdk counts none of this once threads are on, and a threaded receipt never reaches
e5c6427adb35atagen2026-10-25 10:23 UTC248
/// room info, so only our own recount notices it
e5c6427adb35atagen2026-10-25 10:23 UTC249
#[tokio::test]
e5c6427adb35atagen2026-10-25 10:23 UTC250
async fn thread_unreads_follow_threaded_receipts() {
e5c6427adb35atagen2026-10-25 10:23 UTC251
    let server = MatrixMockServer::new().await;
e5c6427adb35atagen2026-10-25 10:23 UTC252
    server
e5c6427adb35atagen2026-10-25 10:23 UTC253
        .mock_sliding_sync()
e5c6427adb35atagen2026-10-25 10:23 UTC254
        .ok(v5::Response::new("1".to_owned()))
e5c6427adb35atagen2026-10-25 10:23 UTC255
        .mount()
e5c6427adb35atagen2026-10-25 10:23 UTC256
        .await;
e5c6427adb35atagen2026-10-25 10:23 UTC257
    let client = server.client_builder().build().await;
e5c6427adb35atagen2026-10-25 10:23 UTC258
    let room_id = room_id!("!threads:localhost");
e5c6427adb35atagen2026-10-25 10:23 UTC259
    server.mock_room_state_encryption().plain().mount().await;
e5c6427adb35atagen2026-10-25 10:23 UTC260
    server.sync_joined_room(&client, room_id).await;
e5c6427adb35atagen2026-10-25 10:23 UTC261
    client.event_cache().subscribe().unwrap();
e5c6427adb35atagen2026-10-25 10:23 UTC262
    let config = EngineConfig::default().with_initial_history_limit(0);
e5c6427adb35atagen2026-10-25 10:23 UTC263
    let (engine, handle, mut events) = Engine::from_client(client.clone(), config).await.unwrap();
e5c6427adb35atagen2026-10-25 10:23 UTC264
    let running = tokio::spawn(engine.run());
e5c6427adb35atagen2026-10-25 10:23 UTC265
    let own = client.user_id().unwrap().to_owned();
e5c6427adb35atagen2026-10-25 10:23 UTC266
e5c6427adb35atagen2026-10-25 10:23 UTC267
    let (root, first, second) = (
e5c6427adb35atagen2026-10-25 10:23 UTC268
        event_id!("$root"),
e5c6427adb35atagen2026-10-25 10:23 UTC269
        event_id!("$first"),
e5c6427adb35atagen2026-10-25 10:23 UTC270
        event_id!("$second"),
e5c6427adb35atagen2026-10-25 10:23 UTC271
    );
e5c6427adb35atagen2026-10-25 10:23 UTC272
    let factory = EventFactory::new().room(room_id).sender(&ALICE);
e5c6427adb35atagen2026-10-25 10:23 UTC273
    server
e5c6427adb35atagen2026-10-25 10:23 UTC274
        .sync_room(
e5c6427adb35atagen2026-10-25 10:23 UTC275
            &client,
e5c6427adb35atagen2026-10-25 10:23 UTC276
            JoinedRoomBuilder::new(room_id)
e5c6427adb35atagen2026-10-25 10:23 UTC277
                .add_timeline_event(factory.text_msg("root").event_id(root))
e5c6427adb35atagen2026-10-25 10:23 UTC278
                .add_timeline_event(
e5c6427adb35atagen2026-10-25 10:23 UTC279
                    factory
e5c6427adb35atagen2026-10-25 10:23 UTC280
                        .text_msg("you there?")
e5c6427adb35atagen2026-10-25 10:23 UTC281
                        .in_thread(root, root)
e5c6427adb35atagen2026-10-25 10:23 UTC282
                        .mentions(Mentions::with_user_ids([own.clone()]))
e5c6427adb35atagen2026-10-25 10:23 UTC283
                        .event_id(first),
e5c6427adb35atagen2026-10-25 10:23 UTC284
                )
e5c6427adb35atagen2026-10-25 10:23 UTC285
                .add_timeline_event(
e5c6427adb35atagen2026-10-25 10:23 UTC286
                    factory
e5c6427adb35atagen2026-10-25 10:23 UTC287
                        .text_msg("hello?")
e5c6427adb35atagen2026-10-25 10:23 UTC288
                        .in_thread(root, first)
e5c6427adb35atagen2026-10-25 10:23 UTC289
                        .event_id(second),
e5c6427adb35atagen2026-10-25 10:23 UTC290
                ),
e5c6427adb35atagen2026-10-25 10:23 UTC291
        )
e5c6427adb35atagen2026-10-25 10:23 UTC292
        .await;
e5c6427adb35atagen2026-10-25 10:23 UTC293
    let unread = thread_unread_once(&handle, &mut events, room_id, |unread| {
e5c6427adb35atagen2026-10-25 10:23 UTC294
        unread.is_some_and(|unread| unread.messages == 2)
e5c6427adb35atagen2026-10-25 10:23 UTC295
    })
e5c6427adb35atagen2026-10-25 10:23 UTC296
    .await;
e5c6427adb35atagen2026-10-25 10:23 UTC297
    assert_eq!(unread.mentions, 1);
e5c6427adb35atagen2026-10-25 10:23 UTC298
    let summary = room(&handle, room_id).await;
e5c6427adb35atagen2026-10-25 10:23 UTC299
    assert!(
e5c6427adb35atagen2026-10-25 10:23 UTC300
        summary.unread_mentions >= 1,
e5c6427adb35atagen2026-10-25 10:23 UTC301
        "the thread mention left the room's count"
e5c6427adb35atagen2026-10-25 10:23 UTC302
    );
e5c6427adb35atagen2026-10-25 10:23 UTC303
e5c6427adb35atagen2026-10-25 10:23 UTC304
    server
e5c6427adb35atagen2026-10-25 10:23 UTC305
        .sync_room(
e5c6427adb35atagen2026-10-25 10:23 UTC306
            &client,
e5c6427adb35atagen2026-10-25 10:23 UTC307
            JoinedRoomBuilder::new(room_id).add_receipt(
e5c6427adb35atagen2026-10-25 10:23 UTC308
                factory
e5c6427adb35atagen2026-10-25 10:23 UTC309
                    .read_receipts()
e5c6427adb35atagen2026-10-25 10:23 UTC310
                    .add(
e5c6427adb35atagen2026-10-25 10:23 UTC311
                        first,
e5c6427adb35atagen2026-10-25 10:23 UTC312
                        &own,
e5c6427adb35atagen2026-10-25 10:23 UTC313
                        ReceiptType::Read,
e5c6427adb35atagen2026-10-25 10:23 UTC314
                        ReceiptThread::Thread(root.to_owned()),
e5c6427adb35atagen2026-10-25 10:23 UTC315
                    )
e5c6427adb35atagen2026-10-25 10:23 UTC316
                    .into_event(),
e5c6427adb35atagen2026-10-25 10:23 UTC317
            ),
e5c6427adb35atagen2026-10-25 10:23 UTC318
        )
e5c6427adb35atagen2026-10-25 10:23 UTC319
        .await;
e5c6427adb35atagen2026-10-25 10:23 UTC320
    let unread = thread_unread_once(&handle, &mut events, room_id, |unread| {
e5c6427adb35atagen2026-10-25 10:23 UTC321
        unread.is_some_and(|unread| unread.messages == 1)
e5c6427adb35atagen2026-10-25 10:23 UTC322
    })
e5c6427adb35atagen2026-10-25 10:23 UTC323
    .await;
e5c6427adb35atagen2026-10-25 10:23 UTC324
    assert_eq!(unread.mentions, 0);
e5c6427adb35atagen2026-10-25 10:23 UTC325
e5c6427adb35atagen2026-10-25 10:23 UTC326
    server
e5c6427adb35atagen2026-10-25 10:23 UTC327
        .sync_room(
e5c6427adb35atagen2026-10-25 10:23 UTC328
            &client,
e5c6427adb35atagen2026-10-25 10:23 UTC329
            JoinedRoomBuilder::new(room_id).add_receipt(
e5c6427adb35atagen2026-10-25 10:23 UTC330
                factory
e5c6427adb35atagen2026-10-25 10:23 UTC331
                    .read_receipts()
e5c6427adb35atagen2026-10-25 10:23 UTC332
                    .add(second, &own, ReceiptType::Read, ReceiptThread::Unthreaded)
e5c6427adb35atagen2026-10-25 10:23 UTC333
                    .into_event(),
e5c6427adb35atagen2026-10-25 10:23 UTC334
            ),
e5c6427adb35atagen2026-10-25 10:23 UTC335
        )
e5c6427adb35atagen2026-10-25 10:23 UTC336
        .await;
e5c6427adb35atagen2026-10-25 10:23 UTC337
    thread_unread_once(&handle, &mut events, room_id, |unread| unread.is_none()).await;
e5c6427adb35atagen2026-10-25 10:23 UTC338
e5c6427adb35atagen2026-10-25 10:23 UTC339
    support::shut_down(&handle, &mut events, running).await;
e5c6427adb35atagen2026-10-25 10:23 UTC340
}
e5c6427adb35atagen2026-10-25 10:23 UTC341
e5c6427adb35atagen2026-10-25 10:23 UTC342
async fn room(handle: &macltok::ClientHandle, room_id: &RoomId) -> macltok::RoomSummary {
e5c6427adb35atagen2026-10-25 10:23 UTC343
    handle
e5c6427adb35atagen2026-10-25 10:23 UTC344
        .rooms()
e5c6427adb35atagen2026-10-25 10:23 UTC345
        .await
e5c6427adb35atagen2026-10-25 10:23 UTC346
        .into_iter()
e5c6427adb35atagen2026-10-25 10:23 UTC347
        .find(|summary| summary.room_id == room_id)
e5c6427adb35atagen2026-10-25 10:23 UTC348
        .expect("synced room was absent from the room summaries")
e5c6427adb35atagen2026-10-25 10:23 UTC349
}
e5c6427adb35atagen2026-10-25 10:23 UTC350
e5c6427adb35atagen2026-10-25 10:23 UTC351
/// waits out `RoomsChanged` until the root's counts satisfy `done`
e5c6427adb35atagen2026-10-25 10:23 UTC352
async fn thread_unread_once(
e5c6427adb35atagen2026-10-25 10:23 UTC353
    handle: &macltok::ClientHandle,
e5c6427adb35atagen2026-10-25 10:23 UTC354
    events: &mut macltok::EventStream,
e5c6427adb35atagen2026-10-25 10:23 UTC355
    room_id: &RoomId,
e5c6427adb35atagen2026-10-25 10:23 UTC356
    done: impl Fn(Option<&ThreadUnread>) -> bool,
e5c6427adb35atagen2026-10-25 10:23 UTC357
) -> ThreadUnread {
e5c6427adb35atagen2026-10-25 10:23 UTC358
    let root = event_id!("$root");
e5c6427adb35atagen2026-10-25 10:23 UTC359
    tokio::time::timeout(Duration::from_secs(10), async {
e5c6427adb35atagen2026-10-25 10:23 UTC360
        loop {
e5c6427adb35atagen2026-10-25 10:23 UTC361
            let summary = room(handle, room_id).await;
e5c6427adb35atagen2026-10-25 10:23 UTC362
            let unread = summary.thread_unreads.get(root);
e5c6427adb35atagen2026-10-25 10:23 UTC363
            if done(unread) {
e5c6427adb35atagen2026-10-25 10:23 UTC364
                return unread.copied().unwrap_or_default();
e5c6427adb35atagen2026-10-25 10:23 UTC365
            }
e5c6427adb35atagen2026-10-25 10:23 UTC366
            support::receive_until(events, |event| matches!(event, Event::RoomsChanged)).await;
e5c6427adb35atagen2026-10-25 10:23 UTC367
        }
e5c6427adb35atagen2026-10-25 10:23 UTC368
    })
e5c6427adb35atagen2026-10-25 10:23 UTC369
    .await
e5c6427adb35atagen2026-10-25 10:23 UTC370
    .expect("thread unreads never settled")
e5c6427adb35atagen2026-10-25 10:23 UTC371
}