use super::{
load_test_vectors_and_sync_chain_index, load_test_vectors_and_sync_chain_index_with_timings,
};
use crate::chain_index::{ChainIndex, SyncTimings};
use std::time::Instant;
use tokio::time::{sleep, Duration};
use zaino_common::status::{Status as _, StatusType};
#[tokio::test(flavor = "multi_thread")]
async fn survives_transient_source_failure() {
let (_blocks, _indexer, index_reader, mockchain) =
load_test_vectors_and_sync_chain_index(true).await;
let start = Instant::now();
mockchain.set_failing(true);
sleep(Duration::from_secs(2)).await;
let status = index_reader.status();
let elapsed = start.elapsed();
assert_ne!(
status,
StatusType::CriticalError,
"sync loop should survive transient source failure, not set CriticalError"
);
let max_time_to_critical = SyncTimings::default().max_backoff_window() + Duration::from_secs(5);
assert!(
elapsed < max_time_to_critical,
"test took {elapsed:?}, which exceeds the maximum possible backoff window"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn escalates_to_critical_after_persistent_failure() {
let timings = SyncTimings::fast();
let (_blocks, _indexer, index_reader, mockchain) =
load_test_vectors_and_sync_chain_index_with_timings(true, timings).await;
let start = Instant::now();
mockchain.set_failing(true);
let max_time_to_critical = timings.max_backoff_window() * 5;
let poll_interval = timings.initial_backoff;
loop {
sleep(poll_interval).await;
if index_reader.status() == StatusType::CriticalError {
break;
}
assert!(
start.elapsed() < max_time_to_critical,
"CriticalError was not reached within {max_time_to_critical:?}"
);
}
let elapsed = start.elapsed();
assert!(
elapsed < max_time_to_critical,
"CriticalError took {elapsed:?}, exceeding the maximum backoff window"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn tip_converges_after_burst_mine() {
let (_blocks, _indexer, index_reader, mockchain) =
load_test_vectors_and_sync_chain_index(true).await;
let initial_tip = mockchain.active_height();
mockchain.mine_blocks(20);
let expected_tip = mockchain.active_height();
assert!(
expected_tip > initial_tip,
"mockchain did not advance: burst mine was a no-op \
(initial_tip={initial_tip}, max_chain_height={})",
mockchain.max_chain_height(),
);
super::poll::poll_until(
"indexer tip to match mined mockchain tip",
Duration::from_secs(10),
Duration::from_millis(25),
|| async {
let tip = index_reader
.snapshot_nonfinalized_state()
.await
.ok()?
.get_nfs_snapshot()?
.best_tip
.height
.0;
(tip == expected_tip).then_some(())
},
)
.await;
let indexer_tip = index_reader
.snapshot_nonfinalized_state()
.await
.unwrap()
.get_nfs_snapshot()
.unwrap()
.best_tip
.height
.0;
assert_eq!(indexer_tip, expected_tip);
}