use alloy::providers::{Provider, ProviderBuilder};
use alloy::transports::mock::Asserter;
use anyhow::Result;
use ethl::rpc::{
RpcError,
config::{CaptureTarget, ProviderSettings},
heads::resolve_capture_target,
};
fn block_json(number: u64) -> serde_json::Value {
serde_json::json!({
"hash": "0x0000000000000000000000000000000000000000000000000000000000000001",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
"miner": "0x0000000000000000000000000000000000000000",
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"logsBloom": format!("0x{}", "0".repeat(512)),
"difficulty": "0x0",
"number": format!("{:#x}", number),
"gasLimit": "0x0",
"gasUsed": "0x0",
"timestamp": "0x0",
"extraData": "0x",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x0000000000000000",
"transactions": [],
"uncles": []
})
}
fn mock_settings(asserter: Asserter) -> ProviderSettings {
let provider = ProviderBuilder::new()
.disable_recommended_fillers()
.connect_mocked_client(asserter)
.erased();
ProviderSettings::from_mock(provider)
}
#[tokio::test]
async fn latest_returns_tip() -> Result<()> {
let asserter = Asserter::new();
asserter.push_success(&serde_json::json!("0x80")); let mut settings = mock_settings(asserter);
settings.capture_target = CaptureTarget::Latest;
let block = resolve_capture_target(&settings).await?;
assert_eq!(block, 128);
Ok(())
}
#[tokio::test]
async fn lag_subtracts_from_tip() -> Result<()> {
let asserter = Asserter::new();
asserter.push_success(&serde_json::json!("0x80")); let mut settings = mock_settings(asserter);
settings.capture_target = CaptureTarget::Lag(10);
let block = resolve_capture_target(&settings).await?;
assert_eq!(block, 118); Ok(())
}
#[tokio::test]
async fn lag_saturates_at_zero() -> Result<()> {
let asserter = Asserter::new();
asserter.push_success(&serde_json::json!("0x05")); let mut settings = mock_settings(asserter);
settings.capture_target = CaptureTarget::Lag(100);
let block = resolve_capture_target(&settings).await?;
assert_eq!(block, 0);
Ok(())
}
#[tokio::test]
async fn finalized_returns_finalized_block_number() -> Result<()> {
let asserter = Asserter::new();
asserter.push_success(&block_json(64));
let mut settings = mock_settings(asserter);
settings.capture_target = CaptureTarget::Finalized;
let block = resolve_capture_target(&settings).await?;
assert_eq!(block, 64);
Ok(())
}
#[tokio::test]
async fn finalized_null_response_is_error() -> Result<()> {
let asserter = Asserter::new();
asserter.push_success(&serde_json::Value::Null);
let mut settings = mock_settings(asserter);
settings.capture_target = CaptureTarget::Finalized;
let result = resolve_capture_target(&settings).await;
assert!(
matches!(result, Err(RpcError::Other(_))),
"expected RpcError::Other for null finalized block, got {result:?}"
);
Ok(())
}