atagen

macltok

public
Star0
Watch0
ffe4843e546db1e01d5ff509132aae1a431c0be9
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]
417da9e719bcatagen2026-10-26 02:40 UTC184
async fn a_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;
417da9e719bcatagen2026-10-26 02:40 UTC219
    room.handle
417da9e719bcatagen2026-10-26 02:40 UTC220
        .retry_recent_history(room_id.to_owned())
417da9e719bcatagen2026-10-26 02:40 UTC221
        .await
417da9e719bcatagen2026-10-26 02:40 UTC222
        .unwrap();
d9b96952ec60atagen2026-10-25 09:07 UTC223
    timeline_until(&mut room, |items| {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC224
        gap_in(items).is_some()
d9b96952ec60atagen2026-10-25 09:07 UTC225
            && has_event(items, old_id)
d9b96952ec60atagen2026-10-25 09:07 UTC226
            && new_ids.iter().all(|id| has_event(items, id))
80381e06a573atagen2026-10-25 03:02 UTC227
    })
80381e06a573atagen2026-10-25 03:02 UTC228
    .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC229
    assert_eq!(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC230
        wait_for_recent(&mut room, room_id).await,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC231
        RecentHistoryState::Loaded
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC232
    );
80381e06a573atagen2026-10-25 03:02 UTC233
    let requests = room.server.received_requests().await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC234
    let messages = requests
80381e06a573atagen2026-10-25 03:02 UTC235
        .iter()
80381e06a573atagen2026-10-25 03:02 UTC236
        .filter(|request| request.url.path().ends_with("/messages"))
80381e06a573atagen2026-10-25 03:02 UTC237
        .collect::<Vec<_>>();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC238
    assert_eq!(messages.len(), 4);
80381e06a573atagen2026-10-25 03:02 UTC239
    assert!(!messages[0].url.query_pairs().any(|(key, _)| key == "from"));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC240
    shut_down(&room.handle, &mut room.events, room.running).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC241
}
80381e06a573atagen2026-10-25 03:02 UTC242
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC243
#[tokio::test]
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC244
async fn explicit_recent_start_coalesces_with_the_automatic_load() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC245
    let room_id = room_id!("!recent-coalesce:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC246
    let fresh_id = event_id!("$coalesced:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC247
    let mut room = start_room(room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC248
    room.server
80381e06a573atagen2026-10-25 03:02 UTC249
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC250
        .ok(RoomMessagesResponseTemplate::default()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC251
            .with_delay(Duration::from_millis(300))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC252
            .events(vec![history_event(room_id, fresh_id, "shared result")]))
80381e06a573atagen2026-10-25 03:02 UTC253
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC254
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC255
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC256
    open_room(&mut room, room_id).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC257
    tokio::time::timeout(Duration::from_secs(5), async {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC258
        loop {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC259
            let requested = room
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC260
                .server
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC261
                .received_requests()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC262
                .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC263
                .unwrap()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC264
                .iter()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC265
                .any(|request| request.url.path().ends_with("/messages"));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC266
            if requested {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC267
                break;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC268
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC269
            tokio::task::yield_now().await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC270
        }
80381e06a573atagen2026-10-25 03:02 UTC271
    })
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC272
    .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC273
    .expect("automatic recent request did not start");
80381e06a573atagen2026-10-25 03:02 UTC274
d9b96952ec60atagen2026-10-25 09:07 UTC275
    room.handle
d9b96952ec60atagen2026-10-25 09:07 UTC276
        .retry_recent_history(room_id.to_owned())
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC277
        .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC278
        .unwrap();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC279
    assert_eq!(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC280
        wait_for_recent(&mut room, room_id).await,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC281
        RecentHistoryState::Loaded
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC282
    );
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC283
    let requests = room.server.received_requests().await.unwrap();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC284
    assert_eq!(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC285
        requests
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC286
            .iter()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC287
            .filter(|request| request.url.path().ends_with("/messages"))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC288
            .count(),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC289
        1
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC290
    );
80381e06a573atagen2026-10-25 03:02 UTC291
    shut_down(&room.handle, &mut room.events, room.running).await;
80381e06a573atagen2026-10-25 03:02 UTC292
}
80381e06a573atagen2026-10-25 03:02 UTC293
80381e06a573atagen2026-10-25 03:02 UTC294
#[cfg(feature = "mirror")]
80381e06a573atagen2026-10-25 03:02 UTC295
#[tokio::test]
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC296
async fn fill_gap_returns_after_the_mirror_contains_the_committed_rows() {
80381e06a573atagen2026-10-25 03:02 UTC297
    let server = MatrixMockServer::new().await;
80381e06a573atagen2026-10-25 03:02 UTC298
    server
80381e06a573atagen2026-10-25 03:02 UTC299
        .mock_sliding_sync()
80381e06a573atagen2026-10-25 03:02 UTC300
        .ok(v5::Response::new("1".to_owned()))
80381e06a573atagen2026-10-25 03:02 UTC301
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC302
        .await;
80381e06a573atagen2026-10-25 03:02 UTC303
    let client = server.client_builder().build().await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC304
    let room_id = room_id!("!fill-gap-mirror:localhost");
80381e06a573atagen2026-10-25 03:02 UTC305
    server.mock_room_state_encryption().plain().mount().await;
80381e06a573atagen2026-10-25 03:02 UTC306
    server.sync_joined_room(&client, room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC307
    let sync_client = client.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC308
    let old_id = event_id!("$gap-old:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC309
    let fresh_id = event_id!("$gap-fresh:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC310
    let middle_id = event_id!("$gap-middle:localhost");
80381e06a573atagen2026-10-25 03:02 UTC311
    let (engine, handle, events) = Engine::from_client(
80381e06a573atagen2026-10-25 03:02 UTC312
        client,
74befbc72bafatagen2026-10-25 11:18 UTC313
        EngineConfig::default()
74befbc72bafatagen2026-10-25 11:18 UTC314
            .with_initial_history_limit(0)
74befbc72bafatagen2026-10-25 11:18 UTC315
            .with_search_backfill_pages_per_room(0),
80381e06a573atagen2026-10-25 03:02 UTC316
    )
80381e06a573atagen2026-10-25 03:02 UTC317
    .await
80381e06a573atagen2026-10-25 03:02 UTC318
    .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC319
    let running = tokio::spawn(engine.run());
80381e06a573atagen2026-10-25 03:02 UTC320
    let (mirror, mirror_handle) = Mirror::new(handle.clone(), events);
80381e06a573atagen2026-10-25 03:02 UTC321
    let mirror_running = tokio::spawn(mirror.run());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC322
    server
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC323
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC324
        .ok(RoomMessagesResponseTemplate::default()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC325
            .end_token("recent-gap")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC326
            .events(vec![history_event(room_id, fresh_id, "new window")]))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC327
        .mock_once()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC328
        .mount()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC329
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC330
    server
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC331
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC332
        .match_from("recent-gap")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC333
        .ok(RoomMessagesResponseTemplate::default().end_token("recent-gap"))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC334
        .mock_once()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC335
        .mount()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC336
        .await;
80381e06a573atagen2026-10-25 03:02 UTC337
    server
80381e06a573atagen2026-10-25 03:02 UTC338
        .sync_room(
80381e06a573atagen2026-10-25 03:02 UTC339
            &sync_client,
80381e06a573atagen2026-10-25 03:02 UTC340
            JoinedRoomBuilder::new(room_id).add_timeline_event(
80381e06a573atagen2026-10-25 03:02 UTC341
                EventFactory::new()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC342
                    .text_msg("old retained")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC343
                    .event_id(old_id)
80381e06a573atagen2026-10-25 03:02 UTC344
                    .sender(&ALICE),
80381e06a573atagen2026-10-25 03:02 UTC345
            ),
80381e06a573atagen2026-10-25 03:02 UTC346
        )
80381e06a573atagen2026-10-25 03:02 UTC347
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC348
    handle.open_room(room_id.to_owned()).await.unwrap();
417da9e719bcatagen2026-10-26 02:40 UTC349
    handle
417da9e719bcatagen2026-10-26 02:40 UTC350
        .retry_recent_history(room_id.to_owned())
417da9e719bcatagen2026-10-26 02:40 UTC351
        .await
417da9e719bcatagen2026-10-26 02:40 UTC352
        .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC353
    let timeline_id = TimelineId::from(room_id.to_owned());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC354
    let gap = tokio::time::timeout(Duration::from_secs(5), async {
80381e06a573atagen2026-10-25 03:02 UTC355
        loop {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC356
            if let Some(gap) = mirror_handle
80381e06a573atagen2026-10-25 03:02 UTC357
                .snapshot()
80381e06a573atagen2026-10-25 03:02 UTC358
                .timelines
80381e06a573atagen2026-10-25 03:02 UTC359
                .get(&timeline_id)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC360
                .and_then(|timeline| gap_in(&timeline.items))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC361
            {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC362
                break gap;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC363
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC364
            tokio::task::yield_now().await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC365
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC366
    })
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC367
    .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC368
    .expect("the recent history gap did not reach the mirror");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC369
    tokio::time::timeout(Duration::from_secs(5), async {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC370
        loop {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC371
            if mirror_handle.snapshot().recent_history.get(room_id)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC372
                == Some(&RecentHistoryState::Loaded)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC373
            {
80381e06a573atagen2026-10-25 03:02 UTC374
                break;
80381e06a573atagen2026-10-25 03:02 UTC375
            }
80381e06a573atagen2026-10-25 03:02 UTC376
            tokio::task::yield_now().await;
80381e06a573atagen2026-10-25 03:02 UTC377
        }
80381e06a573atagen2026-10-25 03:02 UTC378
    })
80381e06a573atagen2026-10-25 03:02 UTC379
    .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC380
    .expect("automatic recent loading did not finish");
80381e06a573atagen2026-10-25 03:02 UTC381
    server
80381e06a573atagen2026-10-25 03:02 UTC382
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC383
        .match_from("recent-gap")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC384
        .ok(RoomMessagesResponseTemplate::default()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC385
            .with_delay(Duration::from_millis(300))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC386
            .events(vec![
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC387
                history_event(room_id, middle_id, "middle page"),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC388
                history_event(room_id, old_id, "old retained overlap"),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC389
            ]))
80381e06a573atagen2026-10-25 03:02 UTC390
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC391
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC392
        .await;
80381e06a573atagen2026-10-25 03:02 UTC393
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC394
    let fill_handle = handle.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC395
    let target_gap_id = gap.id.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC396
    let fill_gap_id = target_gap_id.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC397
    let fill = tokio::spawn(async move {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC398
        fill_handle
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC399
            .fill_history_gap(room_id.to_owned(), fill_gap_id)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC400
            .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC401
    });
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC402
    tokio::time::timeout(Duration::from_secs(5), async {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC403
        loop {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC404
            let snapshot = mirror_handle.snapshot();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC405
            let active_gap = snapshot.repairs.get(room_id).is_some_and(|report| {
d9b96952ec60atagen2026-10-25 09:07 UTC406
                report.gap_id == target_gap_id && report.phase == HistoryRepairPhase::Bridging
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC407
            });
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC408
            if active_gap {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC409
                break;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC410
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC411
            tokio::task::yield_now().await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC412
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC413
    })
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC414
    .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC415
    .expect("the active repair report did not identify its target gap");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC416
    fill.await.unwrap().unwrap();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC417
    let snapshot = mirror_handle.snapshot();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC418
    let timeline = snapshot
80381e06a573atagen2026-10-25 03:02 UTC419
        .timelines
80381e06a573atagen2026-10-25 03:02 UTC420
        .get(&timeline_id)
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC421
        .expect("the room timeline disappeared");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC422
    let event_ids = timeline
80381e06a573atagen2026-10-25 03:02 UTC423
        .items
80381e06a573atagen2026-10-25 03:02 UTC424
        .iter()
80381e06a573atagen2026-10-25 03:02 UTC425
        .filter_map(|item| match item {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC426
            TimelineItem::Event(event) => event.event_id.as_deref(),
80381e06a573atagen2026-10-25 03:02 UTC427
            _ => None,
80381e06a573atagen2026-10-25 03:02 UTC428
        })
80381e06a573atagen2026-10-25 03:02 UTC429
        .collect::<Vec<_>>();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC430
    assert!(event_ids.contains(&fresh_id));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC431
    assert!(event_ids.contains(&middle_id));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC432
    assert!(event_ids.contains(&old_id));
80381e06a573atagen2026-10-25 03:02 UTC433
80381e06a573atagen2026-10-25 03:02 UTC434
    handle.shutdown().await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC435
    assert_eq!(running.await.unwrap().unwrap(), Termination::ShutDown);
80381e06a573atagen2026-10-25 03:02 UTC436
    mirror_running.await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC437
}
80381e06a573atagen2026-10-25 03:02 UTC438
80381e06a573atagen2026-10-25 03:02 UTC439
#[tokio::test]
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC440
async fn cancelling_a_gap_fill_keeps_committed_progress_for_retry() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC441
    let room_id = room_id!("!cancel-gap-fill:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC442
    let old_id = event_id!("$cancel-gap-old:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC443
    let fresh_id = event_id!("$cancel-gap-fresh:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC444
    let middle_id = event_id!("$cancel-gap-middle:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC445
    let mut room = start_room(room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC446
    room.server
80381e06a573atagen2026-10-25 03:02 UTC447
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC448
        .ok(RoomMessagesResponseTemplate::default()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC449
            .end_token("gap-cursor-1")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC450
            .events(vec![history_event(room_id, fresh_id, "recent")]))
80381e06a573atagen2026-10-25 03:02 UTC451
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC452
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC453
        .await;
80381e06a573atagen2026-10-25 03:02 UTC454
    room.server
80381e06a573atagen2026-10-25 03:02 UTC455
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC456
        .match_from("gap-cursor-1")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC457
        .ok(RoomMessagesResponseTemplate::default().end_token("gap-cursor-1"))
80381e06a573atagen2026-10-25 03:02 UTC458
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC459
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC460
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC461
    cache_event_before_open(&room, room_id, old_id, "old cached").await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC462
    open_room(&mut room, room_id).await;
417da9e719bcatagen2026-10-26 02:40 UTC463
    room.handle
417da9e719bcatagen2026-10-26 02:40 UTC464
        .retry_recent_history(room_id.to_owned())
417da9e719bcatagen2026-10-26 02:40 UTC465
        .await
417da9e719bcatagen2026-10-26 02:40 UTC466
        .unwrap();
d9b96952ec60atagen2026-10-25 09:07 UTC467
    timeline_until(&mut room, |items| gap_in(items).is_some()).await;
d9b96952ec60atagen2026-10-25 09:07 UTC468
    let gap = gap_in(&room.items).unwrap();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC469
    assert_eq!(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC470
        wait_for_recent(&mut room, room_id).await,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC471
        RecentHistoryState::Loaded
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC472
    );
80381e06a573atagen2026-10-25 03:02 UTC473
    room.server
80381e06a573atagen2026-10-25 03:02 UTC474
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC475
        .match_from("gap-cursor-1")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC476
        .ok(RoomMessagesResponseTemplate::default()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC477
            .end_token("gap-cursor-2")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC478
            .events(vec![history_event(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC479
                room_id,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC480
                middle_id,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC481
                "committed progress",
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC482
            )]))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC483
        .mock_once()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC484
        .mount()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC485
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC486
    room.server
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC487
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC488
        .match_from("gap-cursor-2")
80381e06a573atagen2026-10-25 03:02 UTC489
        .ok(RoomMessagesResponseTemplate::default()
80381e06a573atagen2026-10-25 03:02 UTC490
            .with_delay(Duration::from_secs(5))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC491
            .events(vec![history_event(room_id, old_id, "old overlap")]))
80381e06a573atagen2026-10-25 03:02 UTC492
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC493
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC494
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC495
    let fill_handle = room.handle.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC496
    let gap_id = gap.id.clone();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC497
    let fill = tokio::spawn(async move {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC498
        fill_handle
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC499
            .fill_history_gap(room_id.to_owned(), gap_id)
80381e06a573atagen2026-10-25 03:02 UTC500
            .await
80381e06a573atagen2026-10-25 03:02 UTC501
    });
80381e06a573atagen2026-10-25 03:02 UTC502
    tokio::time::timeout(Duration::from_secs(5), async {
80381e06a573atagen2026-10-25 03:02 UTC503
        loop {
80381e06a573atagen2026-10-25 03:02 UTC504
            let requested = room
80381e06a573atagen2026-10-25 03:02 UTC505
                .server
80381e06a573atagen2026-10-25 03:02 UTC506
                .received_requests()
80381e06a573atagen2026-10-25 03:02 UTC507
                .await
80381e06a573atagen2026-10-25 03:02 UTC508
                .unwrap()
80381e06a573atagen2026-10-25 03:02 UTC509
                .iter()
80381e06a573atagen2026-10-25 03:02 UTC510
                .any(|request| {
80381e06a573atagen2026-10-25 03:02 UTC511
                    request.url.path().ends_with("/messages")
80381e06a573atagen2026-10-25 03:02 UTC512
                        && request
80381e06a573atagen2026-10-25 03:02 UTC513
                            .url
80381e06a573atagen2026-10-25 03:02 UTC514
                            .query_pairs()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC515
                            .any(|(key, value)| key == "from" && value == "gap-cursor-2")
80381e06a573atagen2026-10-25 03:02 UTC516
                });
80381e06a573atagen2026-10-25 03:02 UTC517
            if requested {
80381e06a573atagen2026-10-25 03:02 UTC518
                break;
80381e06a573atagen2026-10-25 03:02 UTC519
            }
80381e06a573atagen2026-10-25 03:02 UTC520
            tokio::task::yield_now().await;
80381e06a573atagen2026-10-25 03:02 UTC521
        }
80381e06a573atagen2026-10-25 03:02 UTC522
    })
80381e06a573atagen2026-10-25 03:02 UTC523
    .await
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC524
    .expect("the second gap page did not start");
80381e06a573atagen2026-10-25 03:02 UTC525
    room.handle
80381e06a573atagen2026-10-25 03:02 UTC526
        .cancel_history_repair(room_id.to_owned())
80381e06a573atagen2026-10-25 03:02 UTC527
        .await
80381e06a573atagen2026-10-25 03:02 UTC528
        .unwrap();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC529
    let error = fill.await.unwrap().unwrap_err();
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC530
    assert_eq!(error.kind, macltok::ErrorKind::Interrupted);
d9b96952ec60atagen2026-10-25 09:07 UTC531
    let report = wait_for_settled_fill(&mut room).await;
d9b96952ec60atagen2026-10-25 09:07 UTC532
    assert_eq!(report.stop, Some(HistoryRepairStop::Cancelled));
d9b96952ec60atagen2026-10-25 09:07 UTC533
    assert!(report.failure.is_none());
80381e06a573atagen2026-10-25 03:02 UTC534
80381e06a573atagen2026-10-25 03:02 UTC535
    room.server
80381e06a573atagen2026-10-25 03:02 UTC536
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC537
        .match_from("gap-cursor-2")
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC538
        .ok(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC539
            RoomMessagesResponseTemplate::default().events(vec![history_event(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC540
                room_id,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC541
                old_id,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC542
                "old overlap",
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC543
            )]),
80381e06a573atagen2026-10-25 03:02 UTC544
        )
80381e06a573atagen2026-10-25 03:02 UTC545
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC546
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC547
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC548
    room.handle
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC549
        .fill_history_gap(room_id.to_owned(), gap.id)
80381e06a573atagen2026-10-25 03:02 UTC550
        .await
80381e06a573atagen2026-10-25 03:02 UTC551
        .unwrap();
d9b96952ec60atagen2026-10-25 09:07 UTC552
    timeline_until(&mut room, |items| {
d9b96952ec60atagen2026-10-25 09:07 UTC553
        [fresh_id, middle_id, old_id]
d9b96952ec60atagen2026-10-25 09:07 UTC554
            .iter()
d9b96952ec60atagen2026-10-25 09:07 UTC555
            .all(|id| has_event(items, id))
d9b96952ec60atagen2026-10-25 09:07 UTC556
            && gap_in(items).is_none()
80381e06a573atagen2026-10-25 03:02 UTC557
    })
80381e06a573atagen2026-10-25 03:02 UTC558
    .await;
80381e06a573atagen2026-10-25 03:02 UTC559
    shut_down(&room.handle, &mut room.events, room.running).await;
80381e06a573atagen2026-10-25 03:02 UTC560
}
80381e06a573atagen2026-10-25 03:02 UTC561
80381e06a573atagen2026-10-25 03:02 UTC562
#[tokio::test]
417da9e719bcatagen2026-10-26 02:40 UTC563
async fn failed_recent_load_is_public_and_keeps_the_cached_timeline() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC564
    let room_id = room_id!("!failed-recent:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC565
    let old_id = event_id!("$failed-old:localhost");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC566
    let mut room = start_room(room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC567
    room.server
80381e06a573atagen2026-10-25 03:02 UTC568
        .mock_room_messages()
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC569
        .respond_with(ResponseTemplate::new(403).set_body_json(serde_json::json!({
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC570
            "errcode": "M_FORBIDDEN",
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC571
            "error": "history is unavailable",
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC572
        })))
80381e06a573atagen2026-10-25 03:02 UTC573
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC574
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC575
        .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC576
    cache_event_before_open(&room, room_id, old_id, "kept event").await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC577
    open_room(&mut room, room_id).await;
417da9e719bcatagen2026-10-26 02:40 UTC578
    assert!(
417da9e719bcatagen2026-10-26 02:40 UTC579
        room.handle
417da9e719bcatagen2026-10-26 02:40 UTC580
            .retry_recent_history(room_id.to_owned())
417da9e719bcatagen2026-10-26 02:40 UTC581
            .await
417da9e719bcatagen2026-10-26 02:40 UTC582
            .is_err()
417da9e719bcatagen2026-10-26 02:40 UTC583
    );
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC584
    let state = wait_for_recent(&mut room, room_id).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC585
    assert!(matches!(state, RecentHistoryState::Failed(_)));
d9b96952ec60atagen2026-10-25 09:07 UTC586
    shut_down(&room.handle, &mut room.events, room.running).await;
d9b96952ec60atagen2026-10-25 09:07 UTC587
}
d9b96952ec60atagen2026-10-25 09:07 UTC588
d9b96952ec60atagen2026-10-25 09:07 UTC589
#[tokio::test]
d9b96952ec60atagen2026-10-25 09:07 UTC590
async fn closing_during_a_recent_load_settles_idle_not_failed() {
d9b96952ec60atagen2026-10-25 09:07 UTC591
    let room_id = room_id!("!close-recent:localhost");
d9b96952ec60atagen2026-10-25 09:07 UTC592
    let mut room = start_room(room_id).await;
d9b96952ec60atagen2026-10-25 09:07 UTC593
    room.server
d9b96952ec60atagen2026-10-25 09:07 UTC594
        .mock_room_messages()
d9b96952ec60atagen2026-10-25 09:07 UTC595
        .ok(RoomMessagesResponseTemplate::default()
d9b96952ec60atagen2026-10-25 09:07 UTC596
            .with_delay(Duration::from_secs(5))
d9b96952ec60atagen2026-10-25 09:07 UTC597
            .events(vec![history_event(
d9b96952ec60atagen2026-10-25 09:07 UTC598
                room_id,
d9b96952ec60atagen2026-10-25 09:07 UTC599
                event_id!("$never:localhost"),
d9b96952ec60atagen2026-10-25 09:07 UTC600
                "too late",
d9b96952ec60atagen2026-10-25 09:07 UTC601
            )]))
d9b96952ec60atagen2026-10-25 09:07 UTC602
        .mock_once()
d9b96952ec60atagen2026-10-25 09:07 UTC603
        .mount()
d9b96952ec60atagen2026-10-25 09:07 UTC604
        .await;
d9b96952ec60atagen2026-10-25 09:07 UTC605
    open_room(&mut room, room_id).await;
002c79e4c6ecatagen2026-10-25 10:42 UTC606
    tokio::time::timeout(Duration::from_secs(5), async {
002c79e4c6ecatagen2026-10-25 10:42 UTC607
        while !room
002c79e4c6ecatagen2026-10-25 10:42 UTC608
            .server
002c79e4c6ecatagen2026-10-25 10:42 UTC609
            .received_requests()
002c79e4c6ecatagen2026-10-25 10:42 UTC610
            .await
002c79e4c6ecatagen2026-10-25 10:42 UTC611
            .unwrap()
002c79e4c6ecatagen2026-10-25 10:42 UTC612
            .iter()
002c79e4c6ecatagen2026-10-25 10:42 UTC613
            .any(|request| request.url.path().ends_with("/messages"))
002c79e4c6ecatagen2026-10-25 10:42 UTC614
        {
002c79e4c6ecatagen2026-10-25 10:42 UTC615
            tokio::task::yield_now().await;
002c79e4c6ecatagen2026-10-25 10:42 UTC616
        }
002c79e4c6ecatagen2026-10-25 10:42 UTC617
    })
002c79e4c6ecatagen2026-10-25 10:42 UTC618
    .await
002c79e4c6ecatagen2026-10-25 10:42 UTC619
    .expect("the recent load never asked for messages");
d9b96952ec60atagen2026-10-25 09:07 UTC620
    room.handle.close_room(room_id.to_owned()).await.unwrap();
d9b96952ec60atagen2026-10-25 09:07 UTC621
    let mut states = Vec::new();
d9b96952ec60atagen2026-10-25 09:07 UTC622
    next_until(&mut room, |event| {
d9b96952ec60atagen2026-10-25 09:07 UTC623
        if let Event::RecentHistoryChanged { state, .. } = event {
d9b96952ec60atagen2026-10-25 09:07 UTC624
            states.push(state.clone());
d9b96952ec60atagen2026-10-25 09:07 UTC625
        }
d9b96952ec60atagen2026-10-25 09:07 UTC626
        matches!(event, Event::TimelineClosed { timeline_id } if timeline_id.room_id() == room_id)
d9b96952ec60atagen2026-10-25 09:07 UTC627
    })
d9b96952ec60atagen2026-10-25 09:07 UTC628
    .await;
d9b96952ec60atagen2026-10-25 09:07 UTC629
    assert_eq!(
d9b96952ec60atagen2026-10-25 09:07 UTC630
        states,
d9b96952ec60atagen2026-10-25 09:07 UTC631
        [RecentHistoryState::Loading, RecentHistoryState::Idle]
d9b96952ec60atagen2026-10-25 09:07 UTC632
    );
80381e06a573atagen2026-10-25 03:02 UTC633
    shut_down(&room.handle, &mut room.events, room.running).await;
80381e06a573atagen2026-10-25 03:02 UTC634
}
6f47f7cbe75aatagen2026-10-26 02:27 UTC635
6f47f7cbe75aatagen2026-10-26 02:27 UTC636
#[tokio::test]
6f47f7cbe75aatagen2026-10-26 02:27 UTC637
async fn a_reply_whose_target_is_not_loaded_fetches_it() {
6f47f7cbe75aatagen2026-10-26 02:27 UTC638
    let room_id = room_id!("!replies:localhost");
6f47f7cbe75aatagen2026-10-26 02:27 UTC639
    let target_id = event_id!("$target:localhost");
6f47f7cbe75aatagen2026-10-26 02:27 UTC640
    let reply_id = event_id!("$reply:localhost");
6f47f7cbe75aatagen2026-10-26 02:27 UTC641
    let mut room = start_room(room_id).await;
6f47f7cbe75aatagen2026-10-26 02:27 UTC642
    room.server
6f47f7cbe75aatagen2026-10-26 02:27 UTC643
        .mock_room_messages()
6f47f7cbe75aatagen2026-10-26 02:27 UTC644
        .ok(RoomMessagesResponseTemplate::default())
6f47f7cbe75aatagen2026-10-26 02:27 UTC645
        .mount()
6f47f7cbe75aatagen2026-10-26 02:27 UTC646
        .await;
6f47f7cbe75aatagen2026-10-26 02:27 UTC647
    room.server
6f47f7cbe75aatagen2026-10-26 02:27 UTC648
        .mock_room_event()
6f47f7cbe75aatagen2026-10-26 02:27 UTC649
        .match_event_id()
6f47f7cbe75aatagen2026-10-26 02:27 UTC650
        .ok(EventFactory::new()
6f47f7cbe75aatagen2026-10-26 02:27 UTC651
            .room(room_id)
6f47f7cbe75aatagen2026-10-26 02:27 UTC652
            .text_msg("target")
6f47f7cbe75aatagen2026-10-26 02:27 UTC653
            .event_id(target_id)
6f47f7cbe75aatagen2026-10-26 02:27 UTC654
            .sender(&ALICE)
6f47f7cbe75aatagen2026-10-26 02:27 UTC655
            .into_event())
6f47f7cbe75aatagen2026-10-26 02:27 UTC656
        .mount()
6f47f7cbe75aatagen2026-10-26 02:27 UTC657
        .await;
6f47f7cbe75aatagen2026-10-26 02:27 UTC658
    room.server
6f47f7cbe75aatagen2026-10-26 02:27 UTC659
        .sync_room(
6f47f7cbe75aatagen2026-10-26 02:27 UTC660
            &room.sync_client,
6f47f7cbe75aatagen2026-10-26 02:27 UTC661
            JoinedRoomBuilder::new(room_id).add_timeline_event(
6f47f7cbe75aatagen2026-10-26 02:27 UTC662
                EventFactory::new()
6f47f7cbe75aatagen2026-10-26 02:27 UTC663
                    .text_msg("reply")
6f47f7cbe75aatagen2026-10-26 02:27 UTC664
                    .reply_to(target_id)
6f47f7cbe75aatagen2026-10-26 02:27 UTC665
                    .event_id(reply_id)
6f47f7cbe75aatagen2026-10-26 02:27 UTC666
                    .sender(&ALICE),
6f47f7cbe75aatagen2026-10-26 02:27 UTC667
            ),
6f47f7cbe75aatagen2026-10-26 02:27 UTC668
        )
6f47f7cbe75aatagen2026-10-26 02:27 UTC669
        .await;
6f47f7cbe75aatagen2026-10-26 02:27 UTC670
    open_room(&mut room, room_id).await;
6f47f7cbe75aatagen2026-10-26 02:27 UTC671
    timeline_until(&mut room, |items| {
6f47f7cbe75aatagen2026-10-26 02:27 UTC672
        items.iter().any(|item| {
6f47f7cbe75aatagen2026-10-26 02:27 UTC673
            matches!(item, TimelineItem::Event(event)
417da9e719bcatagen2026-10-26 02:40 UTC674
            if event.event_id.as_deref() == Some(reply_id)
417da9e719bcatagen2026-10-26 02:40 UTC675
                && event.reply_to.as_ref().is_some_and(|reply| {
417da9e719bcatagen2026-10-26 02:40 UTC676
                    matches!(reply.details, macltok::ReplyDetails::Ready { .. })
417da9e719bcatagen2026-10-26 02:40 UTC677
                }))
6f47f7cbe75aatagen2026-10-26 02:27 UTC678
        })
6f47f7cbe75aatagen2026-10-26 02:27 UTC679
    })
6f47f7cbe75aatagen2026-10-26 02:27 UTC680
    .await;
6f47f7cbe75aatagen2026-10-26 02:27 UTC681
    shut_down(&room.handle, &mut room.events, room.running).await;
6f47f7cbe75aatagen2026-10-26 02:27 UTC682
}
417da9e719bcatagen2026-10-26 02:40 UTC683
417da9e719bcatagen2026-10-26 02:40 UTC684
#[tokio::test]
417da9e719bcatagen2026-10-26 02:40 UTC685
async fn opening_a_retained_room_only_probes_its_newest_gap() {
417da9e719bcatagen2026-10-26 02:40 UTC686
    let room_id = room_id!("!probe:localhost");
417da9e719bcatagen2026-10-26 02:40 UTC687
    let old_id = event_id!("$probe-old:localhost");
417da9e719bcatagen2026-10-26 02:40 UTC688
    let live_id = event_id!("$probe-live:localhost");
417da9e719bcatagen2026-10-26 02:40 UTC689
    let mut room = start_room(room_id).await;
417da9e719bcatagen2026-10-26 02:40 UTC690
    cache_event_before_open(&room, room_id, old_id, "retained").await;
417da9e719bcatagen2026-10-26 02:40 UTC691
    room.server
417da9e719bcatagen2026-10-26 02:40 UTC692
        .sync_room(
417da9e719bcatagen2026-10-26 02:40 UTC693
            &room.sync_client,
417da9e719bcatagen2026-10-26 02:40 UTC694
            JoinedRoomBuilder::new(room_id)
417da9e719bcatagen2026-10-26 02:40 UTC695
                .add_timeline_event(
417da9e719bcatagen2026-10-26 02:40 UTC696
                    EventFactory::new()
417da9e719bcatagen2026-10-26 02:40 UTC697
                        .text_msg("live")
417da9e719bcatagen2026-10-26 02:40 UTC698
                        .event_id(live_id)
417da9e719bcatagen2026-10-26 02:40 UTC699
                        .sender(&ALICE),
417da9e719bcatagen2026-10-26 02:40 UTC700
                )
417da9e719bcatagen2026-10-26 02:40 UTC701
                .set_timeline_limited()
417da9e719bcatagen2026-10-26 02:40 UTC702
                .set_timeline_prev_batch("sync-edge"),
417da9e719bcatagen2026-10-26 02:40 UTC703
        )
417da9e719bcatagen2026-10-26 02:40 UTC704
        .await;
417da9e719bcatagen2026-10-26 02:40 UTC705
    room.server
417da9e719bcatagen2026-10-26 02:40 UTC706
        .mock_room_messages()
417da9e719bcatagen2026-10-26 02:40 UTC707
        .match_from("sync-edge")
417da9e719bcatagen2026-10-26 02:40 UTC708
        .ok(RoomMessagesResponseTemplate::default()
417da9e719bcatagen2026-10-26 02:40 UTC709
            .end_token("further")
417da9e719bcatagen2026-10-26 02:40 UTC710
            .events(vec![history_event(room_id, old_id, "retained")]))
417da9e719bcatagen2026-10-26 02:40 UTC711
        .mock_once()
417da9e719bcatagen2026-10-26 02:40 UTC712
        .mount()
417da9e719bcatagen2026-10-26 02:40 UTC713
        .await;
417da9e719bcatagen2026-10-26 02:40 UTC714
    open_room(&mut room, room_id).await;
417da9e719bcatagen2026-10-26 02:40 UTC715
    let report = wait_for_settled_fill(&mut room).await;
417da9e719bcatagen2026-10-26 02:40 UTC716
    assert_eq!(report.pages, 1);
417da9e719bcatagen2026-10-26 02:40 UTC717
    assert_eq!(report.stop, None);
417da9e719bcatagen2026-10-26 02:40 UTC718
    timeline_until(&mut room, |items| {
417da9e719bcatagen2026-10-26 02:40 UTC719
        gap_in(items).is_none() && has_event(items, old_id) && has_event(items, live_id)
417da9e719bcatagen2026-10-26 02:40 UTC720
    })
417da9e719bcatagen2026-10-26 02:40 UTC721
    .await;
417da9e719bcatagen2026-10-26 02:40 UTC722
    let requests = room.server.received_requests().await.unwrap();
417da9e719bcatagen2026-10-26 02:40 UTC723
    let messages = requests
417da9e719bcatagen2026-10-26 02:40 UTC724
        .iter()
417da9e719bcatagen2026-10-26 02:40 UTC725
        .filter(|request| request.url.path().ends_with("/messages"))
417da9e719bcatagen2026-10-26 02:40 UTC726
        .count();
417da9e719bcatagen2026-10-26 02:40 UTC727
    assert_eq!(messages, 1);
417da9e719bcatagen2026-10-26 02:40 UTC728
    shut_down(&room.handle, &mut room.events, room.running).await;
417da9e719bcatagen2026-10-26 02:40 UTC729
}