atagen

macltok

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