atagen

macltok

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