atagen

macltok

public
Star0
Watch0
80381e06a573f253c11a8b4c89366a9aa299ca55
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::{
80381e06a573atagen2026-10-25 03:02 UTC6
    ClientHandle, Engine, EngineConfig, EngineError, Event, EventStream, HistoryContinuity,
80381e06a573atagen2026-10-25 03:02 UTC7
    HistoryRepairBudget, HistoryRepairPhase, HistoryRepairPolicy, HistoryRepairStop,
80381e06a573atagen2026-10-25 03:02 UTC8
    HistoryRepairStrategy, Termination, TimelineItem,
80381e06a573atagen2026-10-25 03:02 UTC9
};
80381e06a573atagen2026-10-25 03:02 UTC10
#[cfg(feature = "mirror")]
80381e06a573atagen2026-10-25 03:02 UTC11
use macltok::{Mirror, TimelineId};
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};
80381e06a573atagen2026-10-25 03:02 UTC18
use support::{event_messages, receive_until, 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>>,
80381e06a573atagen2026-10-25 03:02 UTC27
}
80381e06a573atagen2026-10-25 03:02 UTC28
80381e06a573atagen2026-10-25 03:02 UTC29
async fn open_room(room_id: &RoomId) -> TestRoom {
80381e06a573atagen2026-10-25 03:02 UTC30
    let server = MatrixMockServer::new().await;
80381e06a573atagen2026-10-25 03:02 UTC31
    server
80381e06a573atagen2026-10-25 03:02 UTC32
        .mock_sliding_sync()
80381e06a573atagen2026-10-25 03:02 UTC33
        .ok(v5::Response::new("1".to_owned()))
80381e06a573atagen2026-10-25 03:02 UTC34
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC35
        .await;
80381e06a573atagen2026-10-25 03:02 UTC36
    let client = server.client_builder().build().await;
80381e06a573atagen2026-10-25 03:02 UTC37
    server.mock_room_state_encryption().plain().mount().await;
80381e06a573atagen2026-10-25 03:02 UTC38
    server.sync_joined_room(&client, room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC39
    let sync_client = client.clone();
80381e06a573atagen2026-10-25 03:02 UTC40
    let config = EngineConfig::default().with_initial_history_limit(0);
80381e06a573atagen2026-10-25 03:02 UTC41
    let (engine, handle, mut events) = Engine::from_client(client, config).await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC42
    let running = tokio::spawn(engine.run());
80381e06a573atagen2026-10-25 03:02 UTC43
80381e06a573atagen2026-10-25 03:02 UTC44
    handle.open_room(room_id.to_owned()).await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC45
    receive_until(&mut events, |event| {
80381e06a573atagen2026-10-25 03:02 UTC46
        matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id)
80381e06a573atagen2026-10-25 03:02 UTC47
    })
80381e06a573atagen2026-10-25 03:02 UTC48
    .await;
80381e06a573atagen2026-10-25 03:02 UTC49
80381e06a573atagen2026-10-25 03:02 UTC50
    TestRoom {
80381e06a573atagen2026-10-25 03:02 UTC51
        server,
80381e06a573atagen2026-10-25 03:02 UTC52
        sync_client,
80381e06a573atagen2026-10-25 03:02 UTC53
        handle,
80381e06a573atagen2026-10-25 03:02 UTC54
        events,
80381e06a573atagen2026-10-25 03:02 UTC55
        running,
80381e06a573atagen2026-10-25 03:02 UTC56
    }
80381e06a573atagen2026-10-25 03:02 UTC57
}
80381e06a573atagen2026-10-25 03:02 UTC58
80381e06a573atagen2026-10-25 03:02 UTC59
async fn sync_cached_event(room: &mut 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
    receive_until(&mut room.events, |event| {
80381e06a573atagen2026-10-25 03:02 UTC72
        event_messages(event)
80381e06a573atagen2026-10-25 03:02 UTC73
            .into_iter()
80381e06a573atagen2026-10-25 03:02 UTC74
            .any(|event| event.event_id.as_deref() == Some(id))
80381e06a573atagen2026-10-25 03:02 UTC75
    })
80381e06a573atagen2026-10-25 03:02 UTC76
    .await;
80381e06a573atagen2026-10-25 03:02 UTC77
}
80381e06a573atagen2026-10-25 03:02 UTC78
80381e06a573atagen2026-10-25 03:02 UTC79
fn history_event(
80381e06a573atagen2026-10-25 03:02 UTC80
    room_id: &RoomId,
80381e06a573atagen2026-10-25 03:02 UTC81
    id: &EventId,
80381e06a573atagen2026-10-25 03:02 UTC82
    body: &str,
80381e06a573atagen2026-10-25 03:02 UTC83
) -> matrix_sdk::ruma::serde::Raw<matrix_sdk::ruma::events::AnyTimelineEvent> {
80381e06a573atagen2026-10-25 03:02 UTC84
    EventFactory::new()
80381e06a573atagen2026-10-25 03:02 UTC85
        .room(room_id)
80381e06a573atagen2026-10-25 03:02 UTC86
        .text_msg(body)
80381e06a573atagen2026-10-25 03:02 UTC87
        .event_id(id)
80381e06a573atagen2026-10-25 03:02 UTC88
        .sender(&ALICE)
80381e06a573atagen2026-10-25 03:02 UTC89
        .into_raw_timeline()
80381e06a573atagen2026-10-25 03:02 UTC90
}
80381e06a573atagen2026-10-25 03:02 UTC91
80381e06a573atagen2026-10-25 03:02 UTC92
fn report_budget(max_pages: u16) -> HistoryRepairBudget {
80381e06a573atagen2026-10-25 03:02 UTC93
    HistoryRepairBudget {
80381e06a573atagen2026-10-25 03:02 UTC94
        max_pages,
80381e06a573atagen2026-10-25 03:02 UTC95
        ..HistoryRepairBudget::default()
80381e06a573atagen2026-10-25 03:02 UTC96
    }
80381e06a573atagen2026-10-25 03:02 UTC97
}
80381e06a573atagen2026-10-25 03:02 UTC98
80381e06a573atagen2026-10-25 03:02 UTC99
#[tokio::test]
80381e06a573atagen2026-10-25 03:02 UTC100
async fn recent_repair_starts_without_a_cursor_and_keeps_its_end_for_pagination() {
80381e06a573atagen2026-10-25 03:02 UTC101
    let room_id = room_id!("!recent-repair:localhost");
80381e06a573atagen2026-10-25 03:02 UTC102
    let stale_id = event_id!("$stale:localhost");
80381e06a573atagen2026-10-25 03:02 UTC103
    let fresh_id = event_id!("$fresh:localhost");
80381e06a573atagen2026-10-25 03:02 UTC104
    let older_id = event_id!("$older:localhost");
80381e06a573atagen2026-10-25 03:02 UTC105
    let mut room = open_room(room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC106
    sync_cached_event(&mut room, room_id, stale_id, "cached before repair").await;
80381e06a573atagen2026-10-25 03:02 UTC107
80381e06a573atagen2026-10-25 03:02 UTC108
    room.server
80381e06a573atagen2026-10-25 03:02 UTC109
        .mock_room_messages()
80381e06a573atagen2026-10-25 03:02 UTC110
        .ok(RoomMessagesResponseTemplate::default()
80381e06a573atagen2026-10-25 03:02 UTC111
            .end_token("recent-prev")
80381e06a573atagen2026-10-25 03:02 UTC112
            .events(vec![history_event(room_id, fresh_id, "fresh page")]))
80381e06a573atagen2026-10-25 03:02 UTC113
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC114
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC115
        .await;
80381e06a573atagen2026-10-25 03:02 UTC116
    let report = room
80381e06a573atagen2026-10-25 03:02 UTC117
        .handle
80381e06a573atagen2026-10-25 03:02 UTC118
        .history_repair(
80381e06a573atagen2026-10-25 03:02 UTC119
            room_id.to_owned(),
80381e06a573atagen2026-10-25 03:02 UTC120
            Some(HistoryRepairPolicy::StartRecent),
80381e06a573atagen2026-10-25 03:02 UTC121
            HistoryRepairBudget::default(),
80381e06a573atagen2026-10-25 03:02 UTC122
        )
80381e06a573atagen2026-10-25 03:02 UTC123
        .await
80381e06a573atagen2026-10-25 03:02 UTC124
        .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC125
    assert_eq!(report.strategy, HistoryRepairStrategy::Recent);
80381e06a573atagen2026-10-25 03:02 UTC126
    assert_eq!(report.phase, HistoryRepairPhase::Ready);
80381e06a573atagen2026-10-25 03:02 UTC127
    assert!(report.recent_available);
80381e06a573atagen2026-10-25 03:02 UTC128
80381e06a573atagen2026-10-25 03:02 UTC129
    let reset = receive_until(&mut room.events, |event| {
80381e06a573atagen2026-10-25 03:02 UTC130
        matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id)
80381e06a573atagen2026-10-25 03:02 UTC131
            && event_messages(event)
80381e06a573atagen2026-10-25 03:02 UTC132
                .into_iter()
80381e06a573atagen2026-10-25 03:02 UTC133
                .any(|event| event.event_id.as_deref() == Some(fresh_id))
80381e06a573atagen2026-10-25 03:02 UTC134
    })
80381e06a573atagen2026-10-25 03:02 UTC135
    .await;
80381e06a573atagen2026-10-25 03:02 UTC136
    let reset_ids = event_messages(&reset)
80381e06a573atagen2026-10-25 03:02 UTC137
        .into_iter()
80381e06a573atagen2026-10-25 03:02 UTC138
        .filter_map(|event| event.event_id.as_ref().map(ToString::to_string))
80381e06a573atagen2026-10-25 03:02 UTC139
        .collect::<Vec<_>>();
80381e06a573atagen2026-10-25 03:02 UTC140
    assert_eq!(reset_ids, vec![fresh_id.to_string()]);
80381e06a573atagen2026-10-25 03:02 UTC141
80381e06a573atagen2026-10-25 03:02 UTC142
    let requests = room.server.received_requests().await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC143
    let messages = requests
80381e06a573atagen2026-10-25 03:02 UTC144
        .iter()
80381e06a573atagen2026-10-25 03:02 UTC145
        .filter(|request| request.url.path().ends_with("/messages"))
80381e06a573atagen2026-10-25 03:02 UTC146
        .collect::<Vec<_>>();
80381e06a573atagen2026-10-25 03:02 UTC147
    assert_eq!(messages.len(), 1);
80381e06a573atagen2026-10-25 03:02 UTC148
    assert!(!messages[0].url.query_pairs().any(|(key, _)| key == "from"));
80381e06a573atagen2026-10-25 03:02 UTC149
80381e06a573atagen2026-10-25 03:02 UTC150
    room.server
80381e06a573atagen2026-10-25 03:02 UTC151
        .mock_room_messages()
80381e06a573atagen2026-10-25 03:02 UTC152
        .match_from("recent-prev")
80381e06a573atagen2026-10-25 03:02 UTC153
        .ok(
80381e06a573atagen2026-10-25 03:02 UTC154
            RoomMessagesResponseTemplate::default().events(vec![history_event(
80381e06a573atagen2026-10-25 03:02 UTC155
                room_id,
80381e06a573atagen2026-10-25 03:02 UTC156
                older_id,
80381e06a573atagen2026-10-25 03:02 UTC157
                "older page",
80381e06a573atagen2026-10-25 03:02 UTC158
            )]),
80381e06a573atagen2026-10-25 03:02 UTC159
        )
80381e06a573atagen2026-10-25 03:02 UTC160
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC161
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC162
        .await;
80381e06a573atagen2026-10-25 03:02 UTC163
    room.handle
80381e06a573atagen2026-10-25 03:02 UTC164
        .paginate(room_id.to_owned().into(), 10)
80381e06a573atagen2026-10-25 03:02 UTC165
        .await
80381e06a573atagen2026-10-25 03:02 UTC166
        .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC167
    receive_until(&mut room.events, |event| {
80381e06a573atagen2026-10-25 03:02 UTC168
        event_messages(event).into_iter().any(|event| {
80381e06a573atagen2026-10-25 03:02 UTC169
            support::message(event).is_some_and(|message| message.body == "older page")
80381e06a573atagen2026-10-25 03:02 UTC170
        })
80381e06a573atagen2026-10-25 03:02 UTC171
    })
80381e06a573atagen2026-10-25 03:02 UTC172
    .await;
80381e06a573atagen2026-10-25 03:02 UTC173
80381e06a573atagen2026-10-25 03:02 UTC174
    shut_down(&room.handle, &mut room.events, room.running).await;
80381e06a573atagen2026-10-25 03:02 UTC175
}
80381e06a573atagen2026-10-25 03:02 UTC176
80381e06a573atagen2026-10-25 03:02 UTC177
#[cfg(feature = "mirror")]
80381e06a573atagen2026-10-25 03:02 UTC178
#[tokio::test]
80381e06a573atagen2026-10-25 03:02 UTC179
async fn repair_ack_waits_until_the_mirror_has_applied_the_reset_and_report() {
80381e06a573atagen2026-10-25 03:02 UTC180
    let server = MatrixMockServer::new().await;
80381e06a573atagen2026-10-25 03:02 UTC181
    server
80381e06a573atagen2026-10-25 03:02 UTC182
        .mock_sliding_sync()
80381e06a573atagen2026-10-25 03:02 UTC183
        .ok(v5::Response::new("1".to_owned()))
80381e06a573atagen2026-10-25 03:02 UTC184
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC185
        .await;
80381e06a573atagen2026-10-25 03:02 UTC186
    let client = server.client_builder().build().await;
80381e06a573atagen2026-10-25 03:02 UTC187
    let room_id = room_id!("!repair-mirror:localhost");
80381e06a573atagen2026-10-25 03:02 UTC188
    server.mock_room_state_encryption().plain().mount().await;
80381e06a573atagen2026-10-25 03:02 UTC189
    server.sync_joined_room(&client, room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC190
    let sync_client = client.clone();
80381e06a573atagen2026-10-25 03:02 UTC191
    let (engine, handle, events) = Engine::from_client(
80381e06a573atagen2026-10-25 03:02 UTC192
        client,
80381e06a573atagen2026-10-25 03:02 UTC193
        EngineConfig::default().with_initial_history_limit(0),
80381e06a573atagen2026-10-25 03:02 UTC194
    )
80381e06a573atagen2026-10-25 03:02 UTC195
    .await
80381e06a573atagen2026-10-25 03:02 UTC196
    .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC197
    let running = tokio::spawn(engine.run());
80381e06a573atagen2026-10-25 03:02 UTC198
    let (mirror, mirror_handle) = Mirror::new(handle.clone(), events);
80381e06a573atagen2026-10-25 03:02 UTC199
    let mirror_running = tokio::spawn(mirror.run());
80381e06a573atagen2026-10-25 03:02 UTC200
80381e06a573atagen2026-10-25 03:02 UTC201
    handle.open_room(room_id.to_owned()).await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC202
    let stale_id = event_id!("$mirror-stale:localhost");
80381e06a573atagen2026-10-25 03:02 UTC203
    server
80381e06a573atagen2026-10-25 03:02 UTC204
        .sync_room(
80381e06a573atagen2026-10-25 03:02 UTC205
            &sync_client,
80381e06a573atagen2026-10-25 03:02 UTC206
            JoinedRoomBuilder::new(room_id).add_timeline_event(
80381e06a573atagen2026-10-25 03:02 UTC207
                EventFactory::new()
80381e06a573atagen2026-10-25 03:02 UTC208
                    .text_msg("cached event")
80381e06a573atagen2026-10-25 03:02 UTC209
                    .event_id(stale_id)
80381e06a573atagen2026-10-25 03:02 UTC210
                    .sender(&ALICE),
80381e06a573atagen2026-10-25 03:02 UTC211
            ),
80381e06a573atagen2026-10-25 03:02 UTC212
        )
80381e06a573atagen2026-10-25 03:02 UTC213
        .await;
80381e06a573atagen2026-10-25 03:02 UTC214
    let timeline_id = TimelineId::from(room_id.to_owned());
80381e06a573atagen2026-10-25 03:02 UTC215
    tokio::time::timeout(Duration::from_secs(2), async {
80381e06a573atagen2026-10-25 03:02 UTC216
        loop {
80381e06a573atagen2026-10-25 03:02 UTC217
            let has_cached_event = mirror_handle
80381e06a573atagen2026-10-25 03:02 UTC218
                .snapshot()
80381e06a573atagen2026-10-25 03:02 UTC219
                .timelines
80381e06a573atagen2026-10-25 03:02 UTC220
                .get(&timeline_id)
80381e06a573atagen2026-10-25 03:02 UTC221
                .is_some_and(|timeline| {
80381e06a573atagen2026-10-25 03:02 UTC222
                    timeline.items.iter().any(|item| {
80381e06a573atagen2026-10-25 03:02 UTC223
                        matches!(item, TimelineItem::Event(event)
80381e06a573atagen2026-10-25 03:02 UTC224
                            if event.event_id.as_deref() == Some(stale_id))
80381e06a573atagen2026-10-25 03:02 UTC225
                    })
80381e06a573atagen2026-10-25 03:02 UTC226
                });
80381e06a573atagen2026-10-25 03:02 UTC227
            if has_cached_event {
80381e06a573atagen2026-10-25 03:02 UTC228
                break;
80381e06a573atagen2026-10-25 03:02 UTC229
            }
80381e06a573atagen2026-10-25 03:02 UTC230
            tokio::task::yield_now().await;
80381e06a573atagen2026-10-25 03:02 UTC231
        }
80381e06a573atagen2026-10-25 03:02 UTC232
    })
80381e06a573atagen2026-10-25 03:02 UTC233
    .await
80381e06a573atagen2026-10-25 03:02 UTC234
    .expect("the cached timeline event did not reach the mirror");
80381e06a573atagen2026-10-25 03:02 UTC235
80381e06a573atagen2026-10-25 03:02 UTC236
    let fresh_id = event_id!("$mirror-fresh:localhost");
80381e06a573atagen2026-10-25 03:02 UTC237
    server
80381e06a573atagen2026-10-25 03:02 UTC238
        .mock_room_messages()
80381e06a573atagen2026-10-25 03:02 UTC239
        .ok(
80381e06a573atagen2026-10-25 03:02 UTC240
            RoomMessagesResponseTemplate::default().events(vec![history_event(
80381e06a573atagen2026-10-25 03:02 UTC241
                room_id,
80381e06a573atagen2026-10-25 03:02 UTC242
                fresh_id,
80381e06a573atagen2026-10-25 03:02 UTC243
                "fresh event",
80381e06a573atagen2026-10-25 03:02 UTC244
            )]),
80381e06a573atagen2026-10-25 03:02 UTC245
        )
80381e06a573atagen2026-10-25 03:02 UTC246
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC247
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC248
        .await;
80381e06a573atagen2026-10-25 03:02 UTC249
    let report = handle
80381e06a573atagen2026-10-25 03:02 UTC250
        .history_repair(
80381e06a573atagen2026-10-25 03:02 UTC251
            room_id.to_owned(),
80381e06a573atagen2026-10-25 03:02 UTC252
            Some(HistoryRepairPolicy::StartRecent),
80381e06a573atagen2026-10-25 03:02 UTC253
            HistoryRepairBudget::default(),
80381e06a573atagen2026-10-25 03:02 UTC254
        )
80381e06a573atagen2026-10-25 03:02 UTC255
        .await
80381e06a573atagen2026-10-25 03:02 UTC256
        .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC257
80381e06a573atagen2026-10-25 03:02 UTC258
    let state = mirror_handle.snapshot();
80381e06a573atagen2026-10-25 03:02 UTC259
    assert_eq!(state.repairs.get(room_id), Some(&report));
80381e06a573atagen2026-10-25 03:02 UTC260
    let timeline = state
80381e06a573atagen2026-10-25 03:02 UTC261
        .timelines
80381e06a573atagen2026-10-25 03:02 UTC262
        .get(&timeline_id)
80381e06a573atagen2026-10-25 03:02 UTC263
        .expect("the repaired timeline was not applied before acknowledgement");
80381e06a573atagen2026-10-25 03:02 UTC264
    let ids = timeline
80381e06a573atagen2026-10-25 03:02 UTC265
        .items
80381e06a573atagen2026-10-25 03:02 UTC266
        .iter()
80381e06a573atagen2026-10-25 03:02 UTC267
        .filter_map(|item| match item {
80381e06a573atagen2026-10-25 03:02 UTC268
            TimelineItem::Event(event) => event.event_id.as_ref().map(ToString::to_string),
80381e06a573atagen2026-10-25 03:02 UTC269
            _ => None,
80381e06a573atagen2026-10-25 03:02 UTC270
        })
80381e06a573atagen2026-10-25 03:02 UTC271
        .collect::<Vec<_>>();
80381e06a573atagen2026-10-25 03:02 UTC272
    assert_eq!(ids, vec![fresh_id.to_string()]);
80381e06a573atagen2026-10-25 03:02 UTC273
80381e06a573atagen2026-10-25 03:02 UTC274
    handle.shutdown().await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC275
    assert_eq!(running.await.unwrap().unwrap(), Termination::ShutDown);
80381e06a573atagen2026-10-25 03:02 UTC276
    mirror_running.await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC277
}
80381e06a573atagen2026-10-25 03:02 UTC278
80381e06a573atagen2026-10-25 03:02 UTC279
#[tokio::test]
80381e06a573atagen2026-10-25 03:02 UTC280
async fn failed_recent_fetch_leaves_the_existing_timeline_in_place() {
80381e06a573atagen2026-10-25 03:02 UTC281
    let room_id = room_id!("!failed-repair:localhost");
80381e06a573atagen2026-10-25 03:02 UTC282
    let stale_id = event_id!("$kept:localhost");
80381e06a573atagen2026-10-25 03:02 UTC283
    let mut room = open_room(room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC284
    sync_cached_event(&mut room, room_id, stale_id, "keep this event").await;
80381e06a573atagen2026-10-25 03:02 UTC285
80381e06a573atagen2026-10-25 03:02 UTC286
    room.server
80381e06a573atagen2026-10-25 03:02 UTC287
        .mock_room_messages()
80381e06a573atagen2026-10-25 03:02 UTC288
        .respond_with(ResponseTemplate::new(403).set_body_json(serde_json::json!({
80381e06a573atagen2026-10-25 03:02 UTC289
            "errcode": "M_FORBIDDEN",
80381e06a573atagen2026-10-25 03:02 UTC290
            "error": "history is unavailable",
80381e06a573atagen2026-10-25 03:02 UTC291
        })))
80381e06a573atagen2026-10-25 03:02 UTC292
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC293
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC294
        .await;
80381e06a573atagen2026-10-25 03:02 UTC295
    let report = room
80381e06a573atagen2026-10-25 03:02 UTC296
        .handle
80381e06a573atagen2026-10-25 03:02 UTC297
        .history_repair(
80381e06a573atagen2026-10-25 03:02 UTC298
            room_id.to_owned(),
80381e06a573atagen2026-10-25 03:02 UTC299
            Some(HistoryRepairPolicy::StartRecent),
80381e06a573atagen2026-10-25 03:02 UTC300
            HistoryRepairBudget::default(),
80381e06a573atagen2026-10-25 03:02 UTC301
        )
80381e06a573atagen2026-10-25 03:02 UTC302
        .await
80381e06a573atagen2026-10-25 03:02 UTC303
        .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC304
    assert_eq!(report.phase, HistoryRepairPhase::Paused);
80381e06a573atagen2026-10-25 03:02 UTC305
    assert!(report.failure.is_some());
80381e06a573atagen2026-10-25 03:02 UTC306
80381e06a573atagen2026-10-25 03:02 UTC307
    let mut reset_after_failure = false;
80381e06a573atagen2026-10-25 03:02 UTC308
    while let Ok(event) = room.events.try_recv() {
80381e06a573atagen2026-10-25 03:02 UTC309
        reset_after_failure |= matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id);
80381e06a573atagen2026-10-25 03:02 UTC310
    }
80381e06a573atagen2026-10-25 03:02 UTC311
    assert!(!reset_after_failure);
80381e06a573atagen2026-10-25 03:02 UTC312
80381e06a573atagen2026-10-25 03:02 UTC313
    shut_down(&room.handle, &mut room.events, room.running).await;
80381e06a573atagen2026-10-25 03:02 UTC314
}
80381e06a573atagen2026-10-25 03:02 UTC315
80381e06a573atagen2026-10-25 03:02 UTC316
#[tokio::test]
80381e06a573atagen2026-10-25 03:02 UTC317
async fn cancelling_a_delayed_bridge_page_preserves_the_recent_stage() {
80381e06a573atagen2026-10-25 03:02 UTC318
    let room_id = room_id!("!cancel-repair:localhost");
80381e06a573atagen2026-10-25 03:02 UTC319
    let anchor_id = event_id!("$cancel-anchor:localhost");
80381e06a573atagen2026-10-25 03:02 UTC320
    let recent_id = event_id!("$cancel-recent:localhost");
80381e06a573atagen2026-10-25 03:02 UTC321
    let mut room = open_room(room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC322
    sync_cached_event(&mut room, room_id, anchor_id, "cached anchor").await;
80381e06a573atagen2026-10-25 03:02 UTC323
80381e06a573atagen2026-10-25 03:02 UTC324
    room.server
80381e06a573atagen2026-10-25 03:02 UTC325
        .mock_room_messages()
80381e06a573atagen2026-10-25 03:02 UTC326
        .ok(RoomMessagesResponseTemplate::default()
80381e06a573atagen2026-10-25 03:02 UTC327
            .end_token("cancel-prev")
80381e06a573atagen2026-10-25 03:02 UTC328
            .events(vec![history_event(room_id, recent_id, "recent")]))
80381e06a573atagen2026-10-25 03:02 UTC329
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC330
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC331
        .await;
80381e06a573atagen2026-10-25 03:02 UTC332
    let initial = room
80381e06a573atagen2026-10-25 03:02 UTC333
        .handle
80381e06a573atagen2026-10-25 03:02 UTC334
        .history_repair(
80381e06a573atagen2026-10-25 03:02 UTC335
            room_id.to_owned(),
80381e06a573atagen2026-10-25 03:02 UTC336
            Some(HistoryRepairPolicy::Bridge),
80381e06a573atagen2026-10-25 03:02 UTC337
            report_budget(1),
80381e06a573atagen2026-10-25 03:02 UTC338
        )
80381e06a573atagen2026-10-25 03:02 UTC339
        .await
80381e06a573atagen2026-10-25 03:02 UTC340
        .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC341
    assert_eq!(initial.stop, Some(HistoryRepairStop::Budget));
80381e06a573atagen2026-10-25 03:02 UTC342
    assert!(initial.recent_available);
80381e06a573atagen2026-10-25 03:02 UTC343
    receive_until(&mut room.events, |event| {
80381e06a573atagen2026-10-25 03:02 UTC344
        matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id)
80381e06a573atagen2026-10-25 03:02 UTC345
            && event_messages(event)
80381e06a573atagen2026-10-25 03:02 UTC346
                .into_iter()
80381e06a573atagen2026-10-25 03:02 UTC347
                .any(|event| event.event_id.as_deref() == Some(recent_id))
80381e06a573atagen2026-10-25 03:02 UTC348
    })
80381e06a573atagen2026-10-25 03:02 UTC349
    .await;
80381e06a573atagen2026-10-25 03:02 UTC350
80381e06a573atagen2026-10-25 03:02 UTC351
    room.server
80381e06a573atagen2026-10-25 03:02 UTC352
        .mock_room_messages()
80381e06a573atagen2026-10-25 03:02 UTC353
        .match_from("cancel-prev")
80381e06a573atagen2026-10-25 03:02 UTC354
        .ok(RoomMessagesResponseTemplate::default()
80381e06a573atagen2026-10-25 03:02 UTC355
            .with_delay(Duration::from_secs(5))
80381e06a573atagen2026-10-25 03:02 UTC356
            .events(vec![history_event(room_id, anchor_id, "cached anchor")]))
80381e06a573atagen2026-10-25 03:02 UTC357
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC358
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC359
        .await;
80381e06a573atagen2026-10-25 03:02 UTC360
    let repair_handle = room.handle.clone();
80381e06a573atagen2026-10-25 03:02 UTC361
    let repair = tokio::spawn(async move {
80381e06a573atagen2026-10-25 03:02 UTC362
        repair_handle
80381e06a573atagen2026-10-25 03:02 UTC363
            .continue_history_repair(room_id.to_owned(), HistoryRepairBudget::default())
80381e06a573atagen2026-10-25 03:02 UTC364
            .await
80381e06a573atagen2026-10-25 03:02 UTC365
    });
80381e06a573atagen2026-10-25 03:02 UTC366
    tokio::time::timeout(Duration::from_secs(5), async {
80381e06a573atagen2026-10-25 03:02 UTC367
        loop {
80381e06a573atagen2026-10-25 03:02 UTC368
            let requested = room
80381e06a573atagen2026-10-25 03:02 UTC369
                .server
80381e06a573atagen2026-10-25 03:02 UTC370
                .received_requests()
80381e06a573atagen2026-10-25 03:02 UTC371
                .await
80381e06a573atagen2026-10-25 03:02 UTC372
                .unwrap()
80381e06a573atagen2026-10-25 03:02 UTC373
                .iter()
80381e06a573atagen2026-10-25 03:02 UTC374
                .any(|request| {
80381e06a573atagen2026-10-25 03:02 UTC375
                    request.url.path().ends_with("/messages")
80381e06a573atagen2026-10-25 03:02 UTC376
                        && request
80381e06a573atagen2026-10-25 03:02 UTC377
                            .url
80381e06a573atagen2026-10-25 03:02 UTC378
                            .query_pairs()
80381e06a573atagen2026-10-25 03:02 UTC379
                            .any(|(key, value)| key == "from" && value == "cancel-prev")
80381e06a573atagen2026-10-25 03:02 UTC380
                });
80381e06a573atagen2026-10-25 03:02 UTC381
            if requested {
80381e06a573atagen2026-10-25 03:02 UTC382
                break;
80381e06a573atagen2026-10-25 03:02 UTC383
            }
80381e06a573atagen2026-10-25 03:02 UTC384
            tokio::task::yield_now().await;
80381e06a573atagen2026-10-25 03:02 UTC385
        }
80381e06a573atagen2026-10-25 03:02 UTC386
    })
80381e06a573atagen2026-10-25 03:02 UTC387
    .await
80381e06a573atagen2026-10-25 03:02 UTC388
    .expect("bridge request did not start");
80381e06a573atagen2026-10-25 03:02 UTC389
    room.handle
80381e06a573atagen2026-10-25 03:02 UTC390
        .cancel_history_repair(room_id.to_owned())
80381e06a573atagen2026-10-25 03:02 UTC391
        .await
80381e06a573atagen2026-10-25 03:02 UTC392
        .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC393
    let report = repair.await.unwrap().unwrap();
80381e06a573atagen2026-10-25 03:02 UTC394
    assert_eq!(report.phase, HistoryRepairPhase::Paused);
80381e06a573atagen2026-10-25 03:02 UTC395
    assert_eq!(report.stop, Some(HistoryRepairStop::Cancelled));
80381e06a573atagen2026-10-25 03:02 UTC396
    assert!(report.recent_available);
80381e06a573atagen2026-10-25 03:02 UTC397
    assert_eq!(report.pages, 1);
80381e06a573atagen2026-10-25 03:02 UTC398
80381e06a573atagen2026-10-25 03:02 UTC399
    let mut reset_after_cancel = false;
80381e06a573atagen2026-10-25 03:02 UTC400
    while let Ok(event) = room.events.try_recv() {
80381e06a573atagen2026-10-25 03:02 UTC401
        reset_after_cancel |= matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id);
80381e06a573atagen2026-10-25 03:02 UTC402
    }
80381e06a573atagen2026-10-25 03:02 UTC403
    assert!(!reset_after_cancel);
80381e06a573atagen2026-10-25 03:02 UTC404
80381e06a573atagen2026-10-25 03:02 UTC405
    shut_down(&room.handle, &mut room.events, room.running).await;
80381e06a573atagen2026-10-25 03:02 UTC406
}
80381e06a573atagen2026-10-25 03:02 UTC407
80381e06a573atagen2026-10-25 03:02 UTC408
#[tokio::test]
80381e06a573atagen2026-10-25 03:02 UTC409
async fn bridge_reaches_the_cached_anchor_deduplicates_overlap_and_can_continue_after_budget() {
80381e06a573atagen2026-10-25 03:02 UTC410
    let room_id = room_id!("!bridge-repair:localhost");
80381e06a573atagen2026-10-25 03:02 UTC411
    let anchor_id = event_id!("$anchor:localhost");
80381e06a573atagen2026-10-25 03:02 UTC412
    let middle_id = event_id!("$middle:localhost");
80381e06a573atagen2026-10-25 03:02 UTC413
    let recent_id = event_id!("$recent:localhost");
80381e06a573atagen2026-10-25 03:02 UTC414
    let latest_id = event_id!("$latest:localhost");
80381e06a573atagen2026-10-25 03:02 UTC415
    let mut room = open_room(room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC416
    sync_cached_event(&mut room, room_id, anchor_id, "cached anchor").await;
80381e06a573atagen2026-10-25 03:02 UTC417
80381e06a573atagen2026-10-25 03:02 UTC418
    room.server
80381e06a573atagen2026-10-25 03:02 UTC419
        .mock_room_messages()
80381e06a573atagen2026-10-25 03:02 UTC420
        .ok(RoomMessagesResponseTemplate::default()
80381e06a573atagen2026-10-25 03:02 UTC421
            .end_token("recent-prev")
80381e06a573atagen2026-10-25 03:02 UTC422
            .events(vec![
80381e06a573atagen2026-10-25 03:02 UTC423
                history_event(room_id, recent_id, "recent"),
80381e06a573atagen2026-10-25 03:02 UTC424
                history_event(room_id, middle_id, "middle"),
80381e06a573atagen2026-10-25 03:02 UTC425
            ]))
80381e06a573atagen2026-10-25 03:02 UTC426
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC427
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC428
        .await;
80381e06a573atagen2026-10-25 03:02 UTC429
    let initial = room
80381e06a573atagen2026-10-25 03:02 UTC430
        .handle
80381e06a573atagen2026-10-25 03:02 UTC431
        .history_repair(
80381e06a573atagen2026-10-25 03:02 UTC432
            room_id.to_owned(),
80381e06a573atagen2026-10-25 03:02 UTC433
            Some(HistoryRepairPolicy::Bridge),
80381e06a573atagen2026-10-25 03:02 UTC434
            report_budget(1),
80381e06a573atagen2026-10-25 03:02 UTC435
        )
80381e06a573atagen2026-10-25 03:02 UTC436
        .await
80381e06a573atagen2026-10-25 03:02 UTC437
        .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC438
    assert_eq!(initial.phase, HistoryRepairPhase::Paused);
80381e06a573atagen2026-10-25 03:02 UTC439
    assert_eq!(initial.stop, Some(HistoryRepairStop::Budget));
80381e06a573atagen2026-10-25 03:02 UTC440
    assert!(initial.recent_available);
80381e06a573atagen2026-10-25 03:02 UTC441
80381e06a573atagen2026-10-25 03:02 UTC442
    wiremock::Mock::given(|request: &wiremock::Request| {
80381e06a573atagen2026-10-25 03:02 UTC443
        request.url.path().ends_with("/messages")
80381e06a573atagen2026-10-25 03:02 UTC444
            && !request.url.query_pairs().any(|(key, _)| key == "from")
80381e06a573atagen2026-10-25 03:02 UTC445
    })
80381e06a573atagen2026-10-25 03:02 UTC446
    .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
80381e06a573atagen2026-10-25 03:02 UTC447
        "start": "head-start",
80381e06a573atagen2026-10-25 03:02 UTC448
        "chunk": [
80381e06a573atagen2026-10-25 03:02 UTC449
            history_event(room_id, latest_id, "latest"),
80381e06a573atagen2026-10-25 03:02 UTC450
            history_event(room_id, recent_id, "recent overlap"),
80381e06a573atagen2026-10-25 03:02 UTC451
            history_event(room_id, middle_id, "middle overlap"),
80381e06a573atagen2026-10-25 03:02 UTC452
        ],
80381e06a573atagen2026-10-25 03:02 UTC453
        "state": [],
80381e06a573atagen2026-10-25 03:02 UTC454
    })))
80381e06a573atagen2026-10-25 03:02 UTC455
    .up_to_n_times(1)
80381e06a573atagen2026-10-25 03:02 UTC456
    .expect(1)
80381e06a573atagen2026-10-25 03:02 UTC457
    .mount(room.server.server())
80381e06a573atagen2026-10-25 03:02 UTC458
    .await;
80381e06a573atagen2026-10-25 03:02 UTC459
    room.server
80381e06a573atagen2026-10-25 03:02 UTC460
        .mock_room_messages()
80381e06a573atagen2026-10-25 03:02 UTC461
        .match_from("recent-prev")
80381e06a573atagen2026-10-25 03:02 UTC462
        .ok(RoomMessagesResponseTemplate::default()
80381e06a573atagen2026-10-25 03:02 UTC463
            .end_token("older-prev")
80381e06a573atagen2026-10-25 03:02 UTC464
            .events(vec![
80381e06a573atagen2026-10-25 03:02 UTC465
                history_event(room_id, middle_id, "middle overlap"),
80381e06a573atagen2026-10-25 03:02 UTC466
                history_event(room_id, anchor_id, "cached anchor overlap"),
80381e06a573atagen2026-10-25 03:02 UTC467
            ]))
80381e06a573atagen2026-10-25 03:02 UTC468
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC469
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC470
        .await;
80381e06a573atagen2026-10-25 03:02 UTC471
80381e06a573atagen2026-10-25 03:02 UTC472
    let report = room
80381e06a573atagen2026-10-25 03:02 UTC473
        .handle
80381e06a573atagen2026-10-25 03:02 UTC474
        .continue_history_repair(room_id.to_owned(), HistoryRepairBudget::default())
80381e06a573atagen2026-10-25 03:02 UTC475
        .await
80381e06a573atagen2026-10-25 03:02 UTC476
        .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC477
    assert_eq!(report.strategy, HistoryRepairStrategy::Bridge);
80381e06a573atagen2026-10-25 03:02 UTC478
    assert_eq!(report.phase, HistoryRepairPhase::Ready, "{report:?}");
80381e06a573atagen2026-10-25 03:02 UTC479
    assert_eq!(report.continuity, HistoryContinuity::Verified);
80381e06a573atagen2026-10-25 03:02 UTC480
    assert_eq!(report.pages, 3);
80381e06a573atagen2026-10-25 03:02 UTC481
80381e06a573atagen2026-10-25 03:02 UTC482
    let reset = receive_until(&mut room.events, |event| {
80381e06a573atagen2026-10-25 03:02 UTC483
        matches!(event, Event::TimelineReset { timeline_id, .. } if timeline_id.room_id() == room_id)
80381e06a573atagen2026-10-25 03:02 UTC484
            && event_messages(event)
80381e06a573atagen2026-10-25 03:02 UTC485
                .into_iter()
80381e06a573atagen2026-10-25 03:02 UTC486
                .any(|event| event.event_id.as_deref() == Some(latest_id))
80381e06a573atagen2026-10-25 03:02 UTC487
    })
80381e06a573atagen2026-10-25 03:02 UTC488
    .await;
80381e06a573atagen2026-10-25 03:02 UTC489
    let reset_ids = event_messages(&reset)
80381e06a573atagen2026-10-25 03:02 UTC490
        .into_iter()
80381e06a573atagen2026-10-25 03:02 UTC491
        .filter_map(|event| event.event_id.as_ref().map(ToString::to_string))
80381e06a573atagen2026-10-25 03:02 UTC492
        .collect::<Vec<_>>();
80381e06a573atagen2026-10-25 03:02 UTC493
    assert_eq!(
80381e06a573atagen2026-10-25 03:02 UTC494
        reset_ids,
80381e06a573atagen2026-10-25 03:02 UTC495
        vec![
80381e06a573atagen2026-10-25 03:02 UTC496
            anchor_id.to_string(),
80381e06a573atagen2026-10-25 03:02 UTC497
            middle_id.to_string(),
80381e06a573atagen2026-10-25 03:02 UTC498
            recent_id.to_string(),
80381e06a573atagen2026-10-25 03:02 UTC499
            latest_id.to_string(),
80381e06a573atagen2026-10-25 03:02 UTC500
        ]
80381e06a573atagen2026-10-25 03:02 UTC501
    );
80381e06a573atagen2026-10-25 03:02 UTC502
80381e06a573atagen2026-10-25 03:02 UTC503
    shut_down(&room.handle, &mut room.events, room.running).await;
80381e06a573atagen2026-10-25 03:02 UTC504
}
80381e06a573atagen2026-10-25 03:02 UTC505
80381e06a573atagen2026-10-25 03:02 UTC506
#[tokio::test]
80381e06a573atagen2026-10-25 03:02 UTC507
async fn a_recent_window_containing_the_cached_anchor_overlaps_under_every_policy() {
80381e06a573atagen2026-10-25 03:02 UTC508
    let cases = [
80381e06a573atagen2026-10-25 03:02 UTC509
        (
80381e06a573atagen2026-10-25 03:02 UTC510
            room_id!("!policy-recent:localhost"),
80381e06a573atagen2026-10-25 03:02 UTC511
            event_id!("$policy-recent-anchor:localhost"),
80381e06a573atagen2026-10-25 03:02 UTC512
            HistoryRepairPolicy::StartRecent,
80381e06a573atagen2026-10-25 03:02 UTC513
        ),
80381e06a573atagen2026-10-25 03:02 UTC514
        (
80381e06a573atagen2026-10-25 03:02 UTC515
            room_id!("!policy-bridge:localhost"),
80381e06a573atagen2026-10-25 03:02 UTC516
            event_id!("$policy-bridge-anchor:localhost"),
80381e06a573atagen2026-10-25 03:02 UTC517
            HistoryRepairPolicy::Bridge,
80381e06a573atagen2026-10-25 03:02 UTC518
        ),
80381e06a573atagen2026-10-25 03:02 UTC519
        (
80381e06a573atagen2026-10-25 03:02 UTC520
            room_id!("!policy-short-gap:localhost"),
80381e06a573atagen2026-10-25 03:02 UTC521
            event_id!("$policy-short-anchor:localhost"),
80381e06a573atagen2026-10-25 03:02 UTC522
            HistoryRepairPolicy::BridgeShortGaps { threshold_ms: 0 },
80381e06a573atagen2026-10-25 03:02 UTC523
        ),
80381e06a573atagen2026-10-25 03:02 UTC524
    ];
80381e06a573atagen2026-10-25 03:02 UTC525
80381e06a573atagen2026-10-25 03:02 UTC526
    for (room_id, anchor_id, policy) in cases {
80381e06a573atagen2026-10-25 03:02 UTC527
        let mut room = open_room(room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC528
        sync_cached_event(&mut room, room_id, anchor_id, "cached anchor").await;
80381e06a573atagen2026-10-25 03:02 UTC529
        room.server
80381e06a573atagen2026-10-25 03:02 UTC530
            .mock_room_messages()
80381e06a573atagen2026-10-25 03:02 UTC531
            .ok(
80381e06a573atagen2026-10-25 03:02 UTC532
                RoomMessagesResponseTemplate::default().events(vec![history_event(
80381e06a573atagen2026-10-25 03:02 UTC533
                    room_id,
80381e06a573atagen2026-10-25 03:02 UTC534
                    anchor_id,
80381e06a573atagen2026-10-25 03:02 UTC535
                    "same anchor in recent history",
80381e06a573atagen2026-10-25 03:02 UTC536
                )]),
80381e06a573atagen2026-10-25 03:02 UTC537
            )
80381e06a573atagen2026-10-25 03:02 UTC538
            .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC539
            .mount()
80381e06a573atagen2026-10-25 03:02 UTC540
            .await;
80381e06a573atagen2026-10-25 03:02 UTC541
80381e06a573atagen2026-10-25 03:02 UTC542
        let report = room
80381e06a573atagen2026-10-25 03:02 UTC543
            .handle
80381e06a573atagen2026-10-25 03:02 UTC544
            .history_repair(
80381e06a573atagen2026-10-25 03:02 UTC545
                room_id.to_owned(),
80381e06a573atagen2026-10-25 03:02 UTC546
                Some(policy.clone()),
80381e06a573atagen2026-10-25 03:02 UTC547
                HistoryRepairBudget::default(),
80381e06a573atagen2026-10-25 03:02 UTC548
            )
80381e06a573atagen2026-10-25 03:02 UTC549
            .await
80381e06a573atagen2026-10-25 03:02 UTC550
            .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC551
        assert_eq!(report.requested_policy, policy);
80381e06a573atagen2026-10-25 03:02 UTC552
        assert_eq!(report.strategy, HistoryRepairStrategy::Overlap);
80381e06a573atagen2026-10-25 03:02 UTC553
        assert_eq!(report.continuity, HistoryContinuity::Verified);
80381e06a573atagen2026-10-25 03:02 UTC554
        assert_eq!(report.pages, 1);
80381e06a573atagen2026-10-25 03:02 UTC555
        assert!(report.recent_available);
80381e06a573atagen2026-10-25 03:02 UTC556
80381e06a573atagen2026-10-25 03:02 UTC557
        let requests = room.server.received_requests().await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC558
        assert_eq!(
80381e06a573atagen2026-10-25 03:02 UTC559
            requests
80381e06a573atagen2026-10-25 03:02 UTC560
                .iter()
80381e06a573atagen2026-10-25 03:02 UTC561
                .filter(|request| request.url.path().ends_with("/messages"))
80381e06a573atagen2026-10-25 03:02 UTC562
                .count(),
80381e06a573atagen2026-10-25 03:02 UTC563
            1
80381e06a573atagen2026-10-25 03:02 UTC564
        );
80381e06a573atagen2026-10-25 03:02 UTC565
        shut_down(&room.handle, &mut room.events, room.running).await;
80381e06a573atagen2026-10-25 03:02 UTC566
    }
80381e06a573atagen2026-10-25 03:02 UTC567
}
80381e06a573atagen2026-10-25 03:02 UTC568
80381e06a573atagen2026-10-25 03:02 UTC569
#[tokio::test]
80381e06a573atagen2026-10-25 03:02 UTC570
async fn short_gap_policy_stays_recent_when_coverage_is_unknown() {
80381e06a573atagen2026-10-25 03:02 UTC571
    let room_id = room_id!("!unknown-coverage:localhost");
80381e06a573atagen2026-10-25 03:02 UTC572
    let anchor_id = event_id!("$unknown-anchor:localhost");
80381e06a573atagen2026-10-25 03:02 UTC573
    let fresh_id = event_id!("$unknown-fresh:localhost");
80381e06a573atagen2026-10-25 03:02 UTC574
    let mut room = open_room(room_id).await;
80381e06a573atagen2026-10-25 03:02 UTC575
    sync_cached_event(&mut room, room_id, anchor_id, "cached anchor").await;
80381e06a573atagen2026-10-25 03:02 UTC576
    room.server
80381e06a573atagen2026-10-25 03:02 UTC577
        .mock_room_messages()
80381e06a573atagen2026-10-25 03:02 UTC578
        .ok(RoomMessagesResponseTemplate::default()
80381e06a573atagen2026-10-25 03:02 UTC579
            .end_token("recent-prev")
80381e06a573atagen2026-10-25 03:02 UTC580
            .events(vec![history_event(room_id, fresh_id, "recent window")]))
80381e06a573atagen2026-10-25 03:02 UTC581
        .mock_once()
80381e06a573atagen2026-10-25 03:02 UTC582
        .mount()
80381e06a573atagen2026-10-25 03:02 UTC583
        .await;
80381e06a573atagen2026-10-25 03:02 UTC584
80381e06a573atagen2026-10-25 03:02 UTC585
    let report = room
80381e06a573atagen2026-10-25 03:02 UTC586
        .handle
80381e06a573atagen2026-10-25 03:02 UTC587
        .history_repair(
80381e06a573atagen2026-10-25 03:02 UTC588
            room_id.to_owned(),
80381e06a573atagen2026-10-25 03:02 UTC589
            Some(HistoryRepairPolicy::BridgeShortGaps {
80381e06a573atagen2026-10-25 03:02 UTC590
                threshold_ms: 60_000,
80381e06a573atagen2026-10-25 03:02 UTC591
            }),
80381e06a573atagen2026-10-25 03:02 UTC592
            HistoryRepairBudget::default(),
80381e06a573atagen2026-10-25 03:02 UTC593
        )
80381e06a573atagen2026-10-25 03:02 UTC594
        .await
80381e06a573atagen2026-10-25 03:02 UTC595
        .unwrap();
80381e06a573atagen2026-10-25 03:02 UTC596
    assert_eq!(report.coverage_duration_ms, None);
80381e06a573atagen2026-10-25 03:02 UTC597
    assert!(!report.threshold_evaluated);
80381e06a573atagen2026-10-25 03:02 UTC598
    assert_eq!(report.strategy, HistoryRepairStrategy::Recent);
80381e06a573atagen2026-10-25 03:02 UTC599
    assert_eq!(report.continuity, HistoryContinuity::Unresolved);
80381e06a573atagen2026-10-25 03:02 UTC600
    assert_eq!(report.pages, 1);
80381e06a573atagen2026-10-25 03:02 UTC601
80381e06a573atagen2026-10-25 03:02 UTC602
    let requests = room.server.received_requests().await.unwrap();
80381e06a573atagen2026-10-25 03:02 UTC603
    assert_eq!(
80381e06a573atagen2026-10-25 03:02 UTC604
        requests
80381e06a573atagen2026-10-25 03:02 UTC605
            .iter()
80381e06a573atagen2026-10-25 03:02 UTC606
            .filter(|request| request.url.path().ends_with("/messages"))
80381e06a573atagen2026-10-25 03:02 UTC607
            .count(),
80381e06a573atagen2026-10-25 03:02 UTC608
        1
80381e06a573atagen2026-10-25 03:02 UTC609
    );
80381e06a573atagen2026-10-25 03:02 UTC610
    shut_down(&room.handle, &mut room.events, room.running).await;
80381e06a573atagen2026-10-25 03:02 UTC611
}