atagen

macltok

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

Blame: tests/history_repair.rs

80381e06a573atagen2026-10-25 03:02 UTC1
mod support;
80381e06a573atagen2026-10-25 03:02 UTC2
80381e06a573atagen2026-10-25 03:02 UTC3
use std::time::Duration;
80381e06a573atagen2026-10-25 03:02 UTC4
d9b96952ec60atagen2026-10-25 09:07 UTC5
#[cfg(feature = "mirror")]
d9b96952ec60atagen2026-10-25 09:07 UTC6
use macltok::Mirror;
80381e06a573atagen2026-10-25 03:02 UTC7
use macltok::{
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC8
    ClientHandle, Engine, EngineConfig, EngineError, Event, EventStream, HistoryGap,
d9b96952ec60atagen2026-10-25 09:07 UTC9
    HistoryRepairPhase, HistoryRepairReport, HistoryRepairStop, RecentHistoryState, Termination,
d9b96952ec60atagen2026-10-25 09:07 UTC10
    TimelineId, TimelineItem,
80381e06a573atagen2026-10-25 03:02 UTC11
};
80381e06a573atagen2026-10-25 03:02 UTC12
use matrix_sdk::{
80381e06a573atagen2026-10-25 03:02 UTC13
    Client,
80381e06a573atagen2026-10-25 03:02 UTC14
    ruma::{EventId, RoomId, api::client::sync::sync_events::v5, event_id, room_id},
80381e06a573atagen2026-10-25 03:02 UTC15
    test_utils::mocks::{MatrixMockServer, RoomMessagesResponseTemplate},
80381e06a573atagen2026-10-25 03:02 UTC16
};
80381e06a573atagen2026-10-25 03:02 UTC17
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, event_factory::EventFactory};
d9b96952ec60atagen2026-10-25 09:07 UTC18
use support::shut_down;
80381e06a573atagen2026-10-25 03:02 UTC19
use wiremock::ResponseTemplate;
80381e06a573atagen2026-10-25 03:02 UTC20
80381e06a573atagen2026-10-25 03:02 UTC21
struct TestRoom {
80381e06a573atagen2026-10-25 03:02 UTC22
    server: MatrixMockServer,
80381e06a573atagen2026-10-25 03:02 UTC23
    sync_client: Client,
80381e06a573atagen2026-10-25 03:02 UTC24
    handle: ClientHandle,
80381e06a573atagen2026-10-25 03:02 UTC25
    events: EventStream,
80381e06a573atagen2026-10-25 03:02 UTC26
    running: tokio::task::JoinHandle<Result<Termination, EngineError>>,
d9b96952ec60atagen2026-10-25 09:07 UTC27
    /// the open room's timeline, kept current by every helper that reads events
d9b96952ec60atagen2026-10-25 09:07 UTC28
    items: imbl::Vector<TimelineItem>,
d9b96952ec60atagen2026-10-25 09:07 UTC29
    opened: bool,
80381e06a573atagen2026-10-25 03:02 UTC30
}
80381e06a573atagen2026-10-25 03:02 UTC31
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC32
async fn start_room(room_id: &RoomId) -> TestRoom {
80381e06a573atagen2026-10-25 03:02 UTC33
    let server = MatrixMockServer::new().await;
80381e06a573atagen2026-10-25 03:02 UTC34
    server
80381e06a573atagen2026-10-25 03:02 UTC35
        .mock_sliding_sync()
80381e06a573atagen2026-10-25 03:02 UTC36
        .ok(v5::Response::new("1".to_owned()))
80381e06a573atagen2026-10-25 03:02 UTC37
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC38
        .await;
80381e06a573atagen2026-10-25 03:02 UTC39
    let client = server.client_builder().build().await;
80381e06a573atagen2026-10-25 03:02 UTC40
    server.mock_room_state_encryption().plain().mount().await;
80381e06a573atagen2026-10-25 03:02 UTC41
    server.sync_joined_room(&client, room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC42
    let sync_client = client.clone();
80381e06a573atagen2026-10-25 03:02 UTC43
    let config = EngineConfig::default().with_initial_history_limit(0);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC44
    let (engine, handle, events) = Engine::from_client(client, config).await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC45
    let running = tokio::spawn(engine.run());
80381e06a573atagen2026-10-25 03:02 UTC46
    TestRoom {
80381e06a573atagen2026-10-25 03:02 UTC47
        server,
80381e06a573atagen2026-10-25 03:02 UTC48
        sync_client,
80381e06a573atagen2026-10-25 03:02 UTC49
        handle,
80381e06a573atagen2026-10-25 03:02 UTC50
        events,
80381e06a573atagen2026-10-25 03:02 UTC51
        running,
d9b96952ec60atagen2026-10-25 09:07 UTC52
        items: imbl::Vector::new(),
d9b96952ec60atagen2026-10-25 09:07 UTC53
        opened: false,
80381e06a573atagen2026-10-25 03:02 UTC54
    }
80381e06a573atagen2026-10-25 03:02 UTC55
}
80381e06a573atagen2026-10-25 03:02 UTC56
d9b96952ec60atagen2026-10-25 09:07 UTC57
/// the next event that satisfies `predicate`. only the open itself may reset the room
d9b96952ec60atagen2026-10-25 09:07 UTC58
/// timeline, history after it has to arrive as diffs
d9b96952ec60atagen2026-10-25 09:07 UTC59
async fn next_until(room: &mut TestRoom, mut predicate: impl FnMut(&Event) -> bool) -> Event {
d9b96952ec60atagen2026-10-25 09:07 UTC60
    tokio::time::timeout(Duration::from_secs(5), async {
d9b96952ec60atagen2026-10-25 09:07 UTC61
        loop {
d9b96952ec60atagen2026-10-25 09:07 UTC62
            let event = room.events.recv().await.expect("event stream closed");
d9b96952ec60atagen2026-10-25 09:07 UTC63
            match &event {
d9b96952ec60atagen2026-10-25 09:07 UTC64
                Event::TimelineReset {
d9b96952ec60atagen2026-10-25 09:07 UTC65
                    timeline_id: TimelineId::Room(_),
d9b96952ec60atagen2026-10-25 09:07 UTC66
                    items,
d9b96952ec60atagen2026-10-25 09:07 UTC67
                } => {
d9b96952ec60atagen2026-10-25 09:07 UTC68
                    assert!(!room.opened, "history reset the room timeline");
d9b96952ec60atagen2026-10-25 09:07 UTC69
                    room.opened = true;
d9b96952ec60atagen2026-10-25 09:07 UTC70
                    room.items = items.clone();
d9b96952ec60atagen2026-10-25 09:07 UTC71
                }
d9b96952ec60atagen2026-10-25 09:07 UTC72
                Event::TimelineChanged {
d9b96952ec60atagen2026-10-25 09:07 UTC73
                    timeline_id: TimelineId::Room(_),
d9b96952ec60atagen2026-10-25 09:07 UTC74
                    diffs,
d9b96952ec60atagen2026-10-25 09:07 UTC75
                } => {
d9b96952ec60atagen2026-10-25 09:07 UTC76
                    for diff in diffs.clone() {
d9b96952ec60atagen2026-10-25 09:07 UTC77
                        diff.try_apply(&mut room.items)
d9b96952ec60atagen2026-10-25 09:07 UTC78
                            .expect("diff did not fit the timeline");
d9b96952ec60atagen2026-10-25 09:07 UTC79
                    }
d9b96952ec60atagen2026-10-25 09:07 UTC80
                }
d9b96952ec60atagen2026-10-25 09:07 UTC81
                _ => {}
d9b96952ec60atagen2026-10-25 09:07 UTC82
            }
d9b96952ec60atagen2026-10-25 09:07 UTC83
            if predicate(&event) {
d9b96952ec60atagen2026-10-25 09:07 UTC84
                return event;
d9b96952ec60atagen2026-10-25 09:07 UTC85
            }
d9b96952ec60atagen2026-10-25 09:07 UTC86
        }
d9b96952ec60atagen2026-10-25 09:07 UTC87
    })
d9b96952ec60atagen2026-10-25 09:07 UTC88
    .await
d9b96952ec60atagen2026-10-25 09:07 UTC89
    .expect("expected event did not arrive")
d9b96952ec60atagen2026-10-25 09:07 UTC90
}
d9b96952ec60atagen2026-10-25 09:07 UTC91
d9b96952ec60atagen2026-10-25 09:07 UTC92
async fn timeline_until(
d9b96952ec60atagen2026-10-25 09:07 UTC93
    room: &mut TestRoom,
d9b96952ec60atagen2026-10-25 09:07 UTC94
    predicate: impl Fn(&imbl::Vector<TimelineItem>) -> bool,
d9b96952ec60atagen2026-10-25 09:07 UTC95
) {
d9b96952ec60atagen2026-10-25 09:07 UTC96
    tokio::time::timeout(Duration::from_secs(5), async {
d9b96952ec60atagen2026-10-25 09:07 UTC97
        while !predicate(&room.items) {
d9b96952ec60atagen2026-10-25 09:07 UTC98
            next_until(room, |_| true).await;
d9b96952ec60atagen2026-10-25 09:07 UTC99
        }
d9b96952ec60atagen2026-10-25 09:07 UTC100
    })
d9b96952ec60atagen2026-10-25 09:07 UTC101
    .await
d9b96952ec60atagen2026-10-25 09:07 UTC102
    .expect("the timeline never got there");
d9b96952ec60atagen2026-10-25 09:07 UTC103
}
d9b96952ec60atagen2026-10-25 09:07 UTC104
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC105
async fn open_room(room: &mut TestRoom, room_id: &RoomId) {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC106
    room.handle.open_room(room_id.to_owned()).await.unwrap();
d9b96952ec60atagen2026-10-25 09:07 UTC107
    next_until(room, |event| {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC108
        matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC109
    })
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC110
    .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC111
}
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC112
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC113
async fn cache_event_before_open(room: &TestRoom, room_id: &RoomId, id: &EventId, body: &str) {
80381e06a573atagen2026-10-25 03:02 UTC114
    room.server
80381e06a573atagen2026-10-25 03:02 UTC115
        .sync_room(
80381e06a573atagen2026-10-25 03:02 UTC116
            &room.sync_client,
80381e06a573atagen2026-10-25 03:02 UTC117
            JoinedRoomBuilder::new(room_id).add_timeline_event(
80381e06a573atagen2026-10-25 03:02 UTC118
                EventFactory::new()
80381e06a573atagen2026-10-25 03:02 UTC119
                    .text_msg(body)
80381e06a573atagen2026-10-25 03:02 UTC120
                    .event_id(id)
80381e06a573atagen2026-10-25 03:02 UTC121
                    .sender(&ALICE),
80381e06a573atagen2026-10-25 03:02 UTC122
            ),
80381e06a573atagen2026-10-25 03:02 UTC123
        )
80381e06a573atagen2026-10-25 03:02 UTC124
        .await;
80381e06a573atagen2026-10-25 03:02 UTC125
}
80381e06a573atagen2026-10-25 03:02 UTC126
80381e06a573atagen2026-10-25 03:02 UTC127
fn history_event(
80381e06a573atagen2026-10-25 03:02 UTC128
    room_id: &RoomId,
80381e06a573atagen2026-10-25 03:02 UTC129
    id: &EventId,
80381e06a573atagen2026-10-25 03:02 UTC130
    body: &str,
80381e06a573atagen2026-10-25 03:02 UTC131
) -> matrix_sdk::ruma::serde::Raw<matrix_sdk::ruma::events::AnyTimelineEvent> {
80381e06a573atagen2026-10-25 03:02 UTC132
    EventFactory::new()
80381e06a573atagen2026-10-25 03:02 UTC133
        .room(room_id)
80381e06a573atagen2026-10-25 03:02 UTC134
        .text_msg(body)
80381e06a573atagen2026-10-25 03:02 UTC135
        .event_id(id)
80381e06a573atagen2026-10-25 03:02 UTC136
        .sender(&ALICE)
80381e06a573atagen2026-10-25 03:02 UTC137
        .into_raw_timeline()
80381e06a573atagen2026-10-25 03:02 UTC138
}
80381e06a573atagen2026-10-25 03:02 UTC139
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC140
async fn wait_for_recent(room: &mut TestRoom, room_id: &RoomId) -> RecentHistoryState {
d9b96952ec60atagen2026-10-25 09:07 UTC141
    let event = next_until(room, |event| {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC142
        matches!(event, Event::RecentHistoryChanged { room_id: id, state, .. }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC143
            if id == room_id && !matches!(state, RecentHistoryState::Loading))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC144
    })
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC145
    .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC146
    match event {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC147
        Event::RecentHistoryChanged { state, .. } => state,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC148
        _ => unreachable!(),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC149
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC150
}
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC151
d9b96952ec60atagen2026-10-25 09:07 UTC152
async fn wait_for_settled_fill(room: &mut TestRoom) -> HistoryRepairReport {
d9b96952ec60atagen2026-10-25 09:07 UTC153
    match next_until(room, |event| {
d9b96952ec60atagen2026-10-25 09:07 UTC154
        matches!(event, Event::HistoryRepairChanged(report)
d9b96952ec60atagen2026-10-25 09:07 UTC155
            if report.phase != HistoryRepairPhase::Bridging)
d9b96952ec60atagen2026-10-25 09:07 UTC156
    })
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC157
    .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC158
    {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC159
        Event::HistoryRepairChanged(report) => report,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC160
        _ => unreachable!(),
80381e06a573atagen2026-10-25 03:02 UTC161
    }
80381e06a573atagen2026-10-25 03:02 UTC162
}
80381e06a573atagen2026-10-25 03:02 UTC163
d9b96952ec60atagen2026-10-25 09:07 UTC164
fn has_event(items: &imbl::Vector<TimelineItem>, id: &EventId) -> bool {
d9b96952ec60atagen2026-10-25 09:07 UTC165
    items.iter().any(
d9b96952ec60atagen2026-10-25 09:07 UTC166
        |item| matches!(item, TimelineItem::Event(event) if event.event_id.as_deref() == Some(id)),
d9b96952ec60atagen2026-10-25 09:07 UTC167
    )
d9b96952ec60atagen2026-10-25 09:07 UTC168
}
d9b96952ec60atagen2026-10-25 09:07 UTC169
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC170
fn gap_in(items: &imbl::Vector<TimelineItem>) -> Option<HistoryGap> {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC171
    items.iter().find_map(|item| match item {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC172
        TimelineItem::HistoryGap(gap) => Some(gap.clone()),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC173
        _ => None,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC174
    })
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC175
}
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC176
80381e06a573atagen2026-10-25 03:02 UTC177
#[tokio::test]
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC178
async fn automatic_recent_load_is_bounded_and_keeps_cached_history_behind_a_gap() {
80381e06a573atagen2026-10-25 03:02 UTC179
    let room_id = room_id!("!recent-repair:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC180
    let old_id = event_id!("$old:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC181
    let new_ids = [
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC182
        event_id!("$fresh-a:localhost"),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC183
        event_id!("$fresh-b:localhost"),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC184
        event_id!("$fresh-c:localhost"),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC185
        event_id!("$fresh-d:localhost"),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC186
    ];
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC187
    let mut room = start_room(room_id).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC188
    for (index, id) in new_ids.iter().enumerate() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC189
        let token = format!("recent-{index}");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC190
        let response = RoomMessagesResponseTemplate::default()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC191
            .end_token(token.clone())
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC192
            .events(vec![history_event(room_id, id, "recent event")]);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC193
        if index > 0 {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC194
            let previous = format!("recent-{}", index - 1);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC195
            room.server
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC196
                .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC197
                .match_from(&previous)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC198
                .ok(response)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC199
                .mock_once()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC200
                .mount()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC201
                .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC202
        } else {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC203
            room.server
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC204
                .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC205
                .ok(response)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC206
                .mock_once()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC207
                .mount()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC208
                .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC209
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC210
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC211
    cache_event_before_open(&room, room_id, old_id, "retained old event").await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC212
    open_room(&mut room, room_id).await;
d9b96952ec60atagen2026-10-25 09:07 UTC213
    timeline_until(&mut room, |items| {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC214
        gap_in(items).is_some()
d9b96952ec60atagen2026-10-25 09:07 UTC215
            && has_event(items, old_id)
d9b96952ec60atagen2026-10-25 09:07 UTC216
            && new_ids.iter().all(|id| has_event(items, id))
80381e06a573atagen2026-10-25 03:02 UTC217
    })
80381e06a573atagen2026-10-25 03:02 UTC218
    .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC219
    assert_eq!(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC220
        wait_for_recent(&mut room, room_id).await,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC221
        RecentHistoryState::Loaded
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC222
    );
80381e06a573atagen2026-10-25 03:02 UTC223
    let requests = room.server.received_requests().await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC224
    let messages = requests
80381e06a573atagen2026-10-25 03:02 UTC225
        .iter()
80381e06a573atagen2026-10-25 03:02 UTC226
        .filter(|request| request.url.path().ends_with("/messages"))
80381e06a573atagen2026-10-25 03:02 UTC227
        .collect::<Vec<_>>();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC228
    assert_eq!(messages.len(), 4);
80381e06a573atagen2026-10-25 03:02 UTC229
    assert!(!messages[0].url.query_pairs().any(|(key, _)| key == "from"));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC230
    shut_down(&room.handle, &mut room.events, room.running).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC231
}
80381e06a573atagen2026-10-25 03:02 UTC232
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC233
#[tokio::test]
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC234
async fn explicit_recent_start_coalesces_with_the_automatic_load() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC235
    let room_id = room_id!("!recent-coalesce:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC236
    let fresh_id = event_id!("$coalesced:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC237
    let mut room = start_room(room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC238
    room.server
80381e06a573atagen2026-10-25 03:02 UTC239
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC240
        .ok(RoomMessagesResponseTemplate::default()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC241
            .with_delay(Duration::from_millis(300))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC242
            .events(vec![history_event(room_id, fresh_id, "shared result")]))
80381e06a573atagen2026-10-25 03:02 UTC243
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC244
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC245
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC246
    open_room(&mut room, room_id).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC247
    tokio::time::timeout(Duration::from_secs(5), async {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC248
        loop {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC249
            let requested = room
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC250
                .server
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC251
                .received_requests()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC252
                .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC253
                .unwrap()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC254
                .iter()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC255
                .any(|request| request.url.path().ends_with("/messages"));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC256
            if requested {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC257
                break;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC258
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC259
            tokio::task::yield_now().await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC260
        }
80381e06a573atagen2026-10-25 03:02 UTC261
    })
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC262
    .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC263
    .expect("automatic recent request did not start");
80381e06a573atagen2026-10-25 03:02 UTC264
d9b96952ec60atagen2026-10-25 09:07 UTC265
    room.handle
d9b96952ec60atagen2026-10-25 09:07 UTC266
        .retry_recent_history(room_id.to_owned())
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC267
        .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC268
        .unwrap();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC269
    assert_eq!(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC270
        wait_for_recent(&mut room, room_id).await,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC271
        RecentHistoryState::Loaded
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC272
    );
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC273
    let requests = room.server.received_requests().await.unwrap();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC274
    assert_eq!(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC275
        requests
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC276
            .iter()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC277
            .filter(|request| request.url.path().ends_with("/messages"))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC278
            .count(),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC279
        1
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC280
    );
80381e06a573atagen2026-10-25 03:02 UTC281
    shut_down(&room.handle, &mut room.events, room.running).await;
80381e06a573atagen2026-10-25 03:02 UTC282
}
80381e06a573atagen2026-10-25 03:02 UTC283
80381e06a573atagen2026-10-25 03:02 UTC284
#[cfg(feature = "mirror")]
80381e06a573atagen2026-10-25 03:02 UTC285
#[tokio::test]
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC286
async fn fill_gap_returns_after_the_mirror_contains_the_committed_rows() {
80381e06a573atagen2026-10-25 03:02 UTC287
    let server = MatrixMockServer::new().await;
80381e06a573atagen2026-10-25 03:02 UTC288
    server
80381e06a573atagen2026-10-25 03:02 UTC289
        .mock_sliding_sync()
80381e06a573atagen2026-10-25 03:02 UTC290
        .ok(v5::Response::new("1".to_owned()))
80381e06a573atagen2026-10-25 03:02 UTC291
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC292
        .await;
80381e06a573atagen2026-10-25 03:02 UTC293
    let client = server.client_builder().build().await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC294
    let room_id = room_id!("!fill-gap-mirror:localhost");
80381e06a573atagen2026-10-25 03:02 UTC295
    server.mock_room_state_encryption().plain().mount().await;
80381e06a573atagen2026-10-25 03:02 UTC296
    server.sync_joined_room(&client, room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC297
    let sync_client = client.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC298
    let old_id = event_id!("$gap-old:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC299
    let fresh_id = event_id!("$gap-fresh:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC300
    let middle_id = event_id!("$gap-middle:localhost");
80381e06a573atagen2026-10-25 03:02 UTC301
    let (engine, handle, events) = Engine::from_client(
80381e06a573atagen2026-10-25 03:02 UTC302
        client,
80381e06a573atagen2026-10-25 03:02 UTC303
        EngineConfig::default().with_initial_history_limit(0),
80381e06a573atagen2026-10-25 03:02 UTC304
    )
80381e06a573atagen2026-10-25 03:02 UTC305
    .await
80381e06a573atagen2026-10-25 03:02 UTC306
    .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC307
    let running = tokio::spawn(engine.run());
80381e06a573atagen2026-10-25 03:02 UTC308
    let (mirror, mirror_handle) = Mirror::new(handle.clone(), events);
80381e06a573atagen2026-10-25 03:02 UTC309
    let mirror_running = tokio::spawn(mirror.run());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC310
    server
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC311
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC312
        .ok(RoomMessagesResponseTemplate::default()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC313
            .end_token("recent-gap")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC314
            .events(vec![history_event(room_id, fresh_id, "new window")]))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC315
        .mock_once()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC316
        .mount()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC317
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC318
    server
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC319
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC320
        .match_from("recent-gap")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC321
        .ok(RoomMessagesResponseTemplate::default().end_token("recent-gap"))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC322
        .mock_once()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC323
        .mount()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC324
        .await;
80381e06a573atagen2026-10-25 03:02 UTC325
    server
80381e06a573atagen2026-10-25 03:02 UTC326
        .sync_room(
80381e06a573atagen2026-10-25 03:02 UTC327
            &sync_client,
80381e06a573atagen2026-10-25 03:02 UTC328
            JoinedRoomBuilder::new(room_id).add_timeline_event(
80381e06a573atagen2026-10-25 03:02 UTC329
                EventFactory::new()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC330
                    .text_msg("old retained")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC331
                    .event_id(old_id)
80381e06a573atagen2026-10-25 03:02 UTC332
                    .sender(&ALICE),
80381e06a573atagen2026-10-25 03:02 UTC333
            ),
80381e06a573atagen2026-10-25 03:02 UTC334
        )
80381e06a573atagen2026-10-25 03:02 UTC335
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC336
    handle.open_room(room_id.to_owned()).await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC337
    let timeline_id = TimelineId::from(room_id.to_owned());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC338
    let gap = tokio::time::timeout(Duration::from_secs(5), async {
80381e06a573atagen2026-10-25 03:02 UTC339
        loop {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC340
            if let Some(gap) = mirror_handle
80381e06a573atagen2026-10-25 03:02 UTC341
                .snapshot()
80381e06a573atagen2026-10-25 03:02 UTC342
                .timelines
80381e06a573atagen2026-10-25 03:02 UTC343
                .get(&timeline_id)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC344
                .and_then(|timeline| gap_in(&timeline.items))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC345
            {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC346
                break gap;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC347
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC348
            tokio::task::yield_now().await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC349
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC350
    })
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC351
    .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC352
    .expect("the recent history gap did not reach the mirror");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC353
    tokio::time::timeout(Duration::from_secs(5), async {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC354
        loop {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC355
            if mirror_handle.snapshot().recent_history.get(room_id)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC356
                == Some(&RecentHistoryState::Loaded)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC357
            {
80381e06a573atagen2026-10-25 03:02 UTC358
                break;
80381e06a573atagen2026-10-25 03:02 UTC359
            }
80381e06a573atagen2026-10-25 03:02 UTC360
            tokio::task::yield_now().await;
80381e06a573atagen2026-10-25 03:02 UTC361
        }
80381e06a573atagen2026-10-25 03:02 UTC362
    })
80381e06a573atagen2026-10-25 03:02 UTC363
    .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC364
    .expect("automatic recent loading did not finish");
80381e06a573atagen2026-10-25 03:02 UTC365
    server
80381e06a573atagen2026-10-25 03:02 UTC366
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC367
        .match_from("recent-gap")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC368
        .ok(RoomMessagesResponseTemplate::default()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC369
            .with_delay(Duration::from_millis(300))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC370
            .events(vec![
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC371
                history_event(room_id, middle_id, "middle page"),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC372
                history_event(room_id, old_id, "old retained overlap"),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC373
            ]))
80381e06a573atagen2026-10-25 03:02 UTC374
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC375
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC376
        .await;
80381e06a573atagen2026-10-25 03:02 UTC377
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC378
    let fill_handle = handle.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC379
    let target_gap_id = gap.id.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC380
    let fill_gap_id = target_gap_id.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC381
    let fill = tokio::spawn(async move {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC382
        fill_handle
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC383
            .fill_history_gap(room_id.to_owned(), fill_gap_id)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC384
            .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC385
    });
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC386
    tokio::time::timeout(Duration::from_secs(5), async {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC387
        loop {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC388
            let snapshot = mirror_handle.snapshot();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC389
            let active_gap = snapshot.repairs.get(room_id).is_some_and(|report| {
d9b96952ec60atagen2026-10-25 09:07 UTC390
                report.gap_id == target_gap_id && report.phase == HistoryRepairPhase::Bridging
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC391
            });
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC392
            if active_gap {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC393
                break;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC394
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC395
            tokio::task::yield_now().await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC396
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC397
    })
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC398
    .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC399
    .expect("the active repair report did not identify its target gap");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC400
    fill.await.unwrap().unwrap();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC401
    let snapshot = mirror_handle.snapshot();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC402
    let timeline = snapshot
80381e06a573atagen2026-10-25 03:02 UTC403
        .timelines
80381e06a573atagen2026-10-25 03:02 UTC404
        .get(&timeline_id)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC405
        .expect("the room timeline disappeared");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC406
    let event_ids = timeline
80381e06a573atagen2026-10-25 03:02 UTC407
        .items
80381e06a573atagen2026-10-25 03:02 UTC408
        .iter()
80381e06a573atagen2026-10-25 03:02 UTC409
        .filter_map(|item| match item {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC410
            TimelineItem::Event(event) => event.event_id.as_deref(),
80381e06a573atagen2026-10-25 03:02 UTC411
            _ => None,
80381e06a573atagen2026-10-25 03:02 UTC412
        })
80381e06a573atagen2026-10-25 03:02 UTC413
        .collect::<Vec<_>>();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC414
    assert!(event_ids.contains(&fresh_id));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC415
    assert!(event_ids.contains(&middle_id));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC416
    assert!(event_ids.contains(&old_id));
80381e06a573atagen2026-10-25 03:02 UTC417
80381e06a573atagen2026-10-25 03:02 UTC418
    handle.shutdown().await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC419
    assert_eq!(running.await.unwrap().unwrap(), Termination::ShutDown);
80381e06a573atagen2026-10-25 03:02 UTC420
    mirror_running.await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC421
}
80381e06a573atagen2026-10-25 03:02 UTC422
80381e06a573atagen2026-10-25 03:02 UTC423
#[tokio::test]
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC424
async fn cancelling_a_gap_fill_keeps_committed_progress_for_retry() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC425
    let room_id = room_id!("!cancel-gap-fill:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC426
    let old_id = event_id!("$cancel-gap-old:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC427
    let fresh_id = event_id!("$cancel-gap-fresh:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC428
    let middle_id = event_id!("$cancel-gap-middle:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC429
    let mut room = start_room(room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC430
    room.server
80381e06a573atagen2026-10-25 03:02 UTC431
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC432
        .ok(RoomMessagesResponseTemplate::default()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC433
            .end_token("gap-cursor-1")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC434
            .events(vec![history_event(room_id, fresh_id, "recent")]))
80381e06a573atagen2026-10-25 03:02 UTC435
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC436
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC437
        .await;
80381e06a573atagen2026-10-25 03:02 UTC438
    room.server
80381e06a573atagen2026-10-25 03:02 UTC439
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC440
        .match_from("gap-cursor-1")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC441
        .ok(RoomMessagesResponseTemplate::default().end_token("gap-cursor-1"))
80381e06a573atagen2026-10-25 03:02 UTC442
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC443
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC444
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC445
    cache_event_before_open(&room, room_id, old_id, "old cached").await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC446
    open_room(&mut room, room_id).await;
d9b96952ec60atagen2026-10-25 09:07 UTC447
    timeline_until(&mut room, |items| gap_in(items).is_some()).await;
d9b96952ec60atagen2026-10-25 09:07 UTC448
    let gap = gap_in(&room.items).unwrap();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC449
    assert_eq!(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC450
        wait_for_recent(&mut room, room_id).await,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC451
        RecentHistoryState::Loaded
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC452
    );
80381e06a573atagen2026-10-25 03:02 UTC453
    room.server
80381e06a573atagen2026-10-25 03:02 UTC454
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC455
        .match_from("gap-cursor-1")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC456
        .ok(RoomMessagesResponseTemplate::default()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC457
            .end_token("gap-cursor-2")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC458
            .events(vec![history_event(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC459
                room_id,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC460
                middle_id,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC461
                "committed progress",
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC462
            )]))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC463
        .mock_once()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC464
        .mount()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC465
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC466
    room.server
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC467
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC468
        .match_from("gap-cursor-2")
80381e06a573atagen2026-10-25 03:02 UTC469
        .ok(RoomMessagesResponseTemplate::default()
80381e06a573atagen2026-10-25 03:02 UTC470
            .with_delay(Duration::from_secs(5))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC471
            .events(vec![history_event(room_id, old_id, "old overlap")]))
80381e06a573atagen2026-10-25 03:02 UTC472
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC473
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC474
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC475
    let fill_handle = room.handle.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC476
    let gap_id = gap.id.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC477
    let fill = tokio::spawn(async move {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC478
        fill_handle
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC479
            .fill_history_gap(room_id.to_owned(), gap_id)
80381e06a573atagen2026-10-25 03:02 UTC480
            .await
80381e06a573atagen2026-10-25 03:02 UTC481
    });
80381e06a573atagen2026-10-25 03:02 UTC482
    tokio::time::timeout(Duration::from_secs(5), async {
80381e06a573atagen2026-10-25 03:02 UTC483
        loop {
80381e06a573atagen2026-10-25 03:02 UTC484
            let requested = room
80381e06a573atagen2026-10-25 03:02 UTC485
                .server
80381e06a573atagen2026-10-25 03:02 UTC486
                .received_requests()
80381e06a573atagen2026-10-25 03:02 UTC487
                .await
80381e06a573atagen2026-10-25 03:02 UTC488
                .unwrap()
80381e06a573atagen2026-10-25 03:02 UTC489
                .iter()
80381e06a573atagen2026-10-25 03:02 UTC490
                .any(|request| {
80381e06a573atagen2026-10-25 03:02 UTC491
                    request.url.path().ends_with("/messages")
80381e06a573atagen2026-10-25 03:02 UTC492
                        && request
80381e06a573atagen2026-10-25 03:02 UTC493
                            .url
80381e06a573atagen2026-10-25 03:02 UTC494
                            .query_pairs()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC495
                            .any(|(key, value)| key == "from" && value == "gap-cursor-2")
80381e06a573atagen2026-10-25 03:02 UTC496
                });
80381e06a573atagen2026-10-25 03:02 UTC497
            if requested {
80381e06a573atagen2026-10-25 03:02 UTC498
                break;
80381e06a573atagen2026-10-25 03:02 UTC499
            }
80381e06a573atagen2026-10-25 03:02 UTC500
            tokio::task::yield_now().await;
80381e06a573atagen2026-10-25 03:02 UTC501
        }
80381e06a573atagen2026-10-25 03:02 UTC502
    })
80381e06a573atagen2026-10-25 03:02 UTC503
    .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC504
    .expect("the second gap page did not start");
80381e06a573atagen2026-10-25 03:02 UTC505
    room.handle
80381e06a573atagen2026-10-25 03:02 UTC506
        .cancel_history_repair(room_id.to_owned())
80381e06a573atagen2026-10-25 03:02 UTC507
        .await
80381e06a573atagen2026-10-25 03:02 UTC508
        .unwrap();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC509
    let error = fill.await.unwrap().unwrap_err();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC510
    assert_eq!(error.kind, macltok::ErrorKind::Interrupted);
d9b96952ec60atagen2026-10-25 09:07 UTC511
    let report = wait_for_settled_fill(&mut room).await;
d9b96952ec60atagen2026-10-25 09:07 UTC512
    assert_eq!(report.stop, Some(HistoryRepairStop::Cancelled));
d9b96952ec60atagen2026-10-25 09:07 UTC513
    assert!(report.failure.is_none());
80381e06a573atagen2026-10-25 03:02 UTC514
80381e06a573atagen2026-10-25 03:02 UTC515
    room.server
80381e06a573atagen2026-10-25 03:02 UTC516
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC517
        .match_from("gap-cursor-2")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC518
        .ok(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC519
            RoomMessagesResponseTemplate::default().events(vec![history_event(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC520
                room_id,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC521
                old_id,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC522
                "old overlap",
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC523
            )]),
80381e06a573atagen2026-10-25 03:02 UTC524
        )
80381e06a573atagen2026-10-25 03:02 UTC525
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC526
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC527
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC528
    room.handle
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC529
        .fill_history_gap(room_id.to_owned(), gap.id)
80381e06a573atagen2026-10-25 03:02 UTC530
        .await
80381e06a573atagen2026-10-25 03:02 UTC531
        .unwrap();
d9b96952ec60atagen2026-10-25 09:07 UTC532
    timeline_until(&mut room, |items| {
d9b96952ec60atagen2026-10-25 09:07 UTC533
        [fresh_id, middle_id, old_id]
d9b96952ec60atagen2026-10-25 09:07 UTC534
            .iter()
d9b96952ec60atagen2026-10-25 09:07 UTC535
            .all(|id| has_event(items, id))
d9b96952ec60atagen2026-10-25 09:07 UTC536
            && gap_in(items).is_none()
80381e06a573atagen2026-10-25 03:02 UTC537
    })
80381e06a573atagen2026-10-25 03:02 UTC538
    .await;
80381e06a573atagen2026-10-25 03:02 UTC539
    shut_down(&room.handle, &mut room.events, room.running).await;
80381e06a573atagen2026-10-25 03:02 UTC540
}
80381e06a573atagen2026-10-25 03:02 UTC541
80381e06a573atagen2026-10-25 03:02 UTC542
#[tokio::test]
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC543
async fn failed_automatic_recent_load_is_public_and_keeps_the_cached_timeline() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC544
    let room_id = room_id!("!failed-recent:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC545
    let old_id = event_id!("$failed-old:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC546
    let mut room = start_room(room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC547
    room.server
80381e06a573atagen2026-10-25 03:02 UTC548
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC549
        .respond_with(ResponseTemplate::new(403).set_body_json(serde_json::json!({
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC550
            "errcode": "M_FORBIDDEN",
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC551
            "error": "history is unavailable",
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC552
        })))
80381e06a573atagen2026-10-25 03:02 UTC553
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC554
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC555
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC556
    cache_event_before_open(&room, room_id, old_id, "kept event").await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC557
    open_room(&mut room, room_id).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC558
    let state = wait_for_recent(&mut room, room_id).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC559
    assert!(matches!(state, RecentHistoryState::Failed(_)));
d9b96952ec60atagen2026-10-25 09:07 UTC560
    shut_down(&room.handle, &mut room.events, room.running).await;
d9b96952ec60atagen2026-10-25 09:07 UTC561
}
d9b96952ec60atagen2026-10-25 09:07 UTC562
d9b96952ec60atagen2026-10-25 09:07 UTC563
#[tokio::test]
d9b96952ec60atagen2026-10-25 09:07 UTC564
async fn closing_during_a_recent_load_settles_idle_not_failed() {
d9b96952ec60atagen2026-10-25 09:07 UTC565
    let room_id = room_id!("!close-recent:localhost");
d9b96952ec60atagen2026-10-25 09:07 UTC566
    let mut room = start_room(room_id).await;
d9b96952ec60atagen2026-10-25 09:07 UTC567
    room.server
d9b96952ec60atagen2026-10-25 09:07 UTC568
        .mock_room_messages()
d9b96952ec60atagen2026-10-25 09:07 UTC569
        .ok(RoomMessagesResponseTemplate::default()
d9b96952ec60atagen2026-10-25 09:07 UTC570
            .with_delay(Duration::from_secs(5))
d9b96952ec60atagen2026-10-25 09:07 UTC571
            .events(vec![history_event(
d9b96952ec60atagen2026-10-25 09:07 UTC572
                room_id,
d9b96952ec60atagen2026-10-25 09:07 UTC573
                event_id!("$never:localhost"),
d9b96952ec60atagen2026-10-25 09:07 UTC574
                "too late",
d9b96952ec60atagen2026-10-25 09:07 UTC575
            )]))
d9b96952ec60atagen2026-10-25 09:07 UTC576
        .mock_once()
d9b96952ec60atagen2026-10-25 09:07 UTC577
        .mount()
d9b96952ec60atagen2026-10-25 09:07 UTC578
        .await;
d9b96952ec60atagen2026-10-25 09:07 UTC579
    open_room(&mut room, room_id).await;
d9b96952ec60atagen2026-10-25 09:07 UTC580
    room.handle.close_room(room_id.to_owned()).await.unwrap();
d9b96952ec60atagen2026-10-25 09:07 UTC581
    let mut states = Vec::new();
d9b96952ec60atagen2026-10-25 09:07 UTC582
    next_until(&mut room, |event| {
d9b96952ec60atagen2026-10-25 09:07 UTC583
        if let Event::RecentHistoryChanged { state, .. } = event {
d9b96952ec60atagen2026-10-25 09:07 UTC584
            states.push(state.clone());
d9b96952ec60atagen2026-10-25 09:07 UTC585
        }
d9b96952ec60atagen2026-10-25 09:07 UTC586
        matches!(event, Event::TimelineClosed { timeline_id } if timeline_id.room_id() == room_id)
d9b96952ec60atagen2026-10-25 09:07 UTC587
    })
d9b96952ec60atagen2026-10-25 09:07 UTC588
    .await;
d9b96952ec60atagen2026-10-25 09:07 UTC589
    assert_eq!(
d9b96952ec60atagen2026-10-25 09:07 UTC590
        states,
d9b96952ec60atagen2026-10-25 09:07 UTC591
        [RecentHistoryState::Loading, RecentHistoryState::Idle]
d9b96952ec60atagen2026-10-25 09:07 UTC592
    );
80381e06a573atagen2026-10-25 03:02 UTC593
    shut_down(&room.handle, &mut room.events, room.running).await;
80381e06a573atagen2026-10-25 03:02 UTC594
}