atagen

macltok

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