v0.2.11-pre.011
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
// file: crates/ksp-offchain-transport-lib/tests/market_price_live_smoke.rs
|
||||
// version: 1
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! Opt-in live SOL/USD smokes for the V1 Off-chain Transport market-price providers.
|
||||
|
||||
use std::io::IsTerminal; // rust-rules: trait-import
|
||||
|
||||
const DEXSCREENER_SOL_USDC_ORCA_PAIR: &str = "7qbRF6YsyGuLUVs6Y1q64bdVrfe4ZcUUz1JRdoVNUJnm";
|
||||
|
||||
fn smoke_error(message: &'static str) -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(ksp_offchain_transport_lib::ERROR_CODE_MARKET_PRICE_REFRESH_INVALID, message);
|
||||
}
|
||||
|
||||
fn keyless_setups() -> ksp_core_lib::Result<std::vec::Vec<ksp_offchain_transport_lib::MarketPriceProviderSetup>> {
|
||||
let mut setups = std::vec::Vec::with_capacity(7);
|
||||
let coinbase = match ksp_offchain_transport_lib::MarketPriceCoinbaseExchangeSettings::new(true) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinbaseExchange(coinbase));
|
||||
let coingecko = match ksp_offchain_transport_lib::MarketPriceCoinGeckoSettings::keyless(true) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinGecko(coingecko));
|
||||
let coinmarketcap = match ksp_offchain_transport_lib::MarketPriceCoinMarketCapSettings::keyless(true) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinMarketCap(coinmarketcap));
|
||||
let coinpaprika = match ksp_offchain_transport_lib::MarketPriceCoinPaprikaSettings::new(true) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinPaprika(coinpaprika));
|
||||
let dexscreener =
|
||||
match ksp_offchain_transport_lib::MarketPriceDexScreenerSettings::new(true, std::option::Option::Some(DEXSCREENER_SOL_USDC_ORCA_PAIR.to_owned())) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::DexScreener(dexscreener));
|
||||
let jupiter = match ksp_offchain_transport_lib::MarketPriceJupiterSettings::keyless(true) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::Jupiter(jupiter));
|
||||
let kraken = match ksp_offchain_transport_lib::MarketPriceKrakenSettings::new(true) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::Kraken(kraken));
|
||||
return std::result::Result::Ok(setups);
|
||||
}
|
||||
|
||||
fn read_secret_line(label: &'static str) -> ksp_core_lib::Result<std::string::String> {
|
||||
let mut secret = std::string::String::new();
|
||||
match std::io::stdin().read_line(&mut secret) {
|
||||
std::result::Result::Ok(_) => {},
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(smoke_error("keyed market-price smoke stdin is unreadable")),
|
||||
}
|
||||
let secret = secret.trim().to_owned();
|
||||
if secret.is_empty() {
|
||||
return std::result::Result::Err(smoke_error(label));
|
||||
}
|
||||
return std::result::Result::Ok(secret);
|
||||
}
|
||||
|
||||
fn keyed_setups() -> ksp_core_lib::Result<std::vec::Vec<ksp_offchain_transport_lib::MarketPriceProviderSetup>> {
|
||||
if std::io::stdin().is_terminal() {
|
||||
return std::result::Result::Err(smoke_error("pipe four provider API keys to the keyed market-price smoke on stdin"));
|
||||
}
|
||||
let birdeye_key = match read_secret_line("Birdeye API key from smoke stdin must not be empty") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let coingecko_key = match read_secret_line("CoinGecko Demo API key from smoke stdin must not be empty") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let coinmarketcap_key = match read_secret_line("CoinMarketCap Basic API key from smoke stdin must not be empty") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let jupiter_key = match read_secret_line("Jupiter Free API key from smoke stdin must not be empty") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut setups = std::vec::Vec::with_capacity(4);
|
||||
let birdeye = match ksp_offchain_transport_lib::MarketPriceBirdeyeSettings::new(true, std::option::Option::Some(birdeye_key)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::Birdeye(birdeye));
|
||||
let coingecko = match ksp_offchain_transport_lib::MarketPriceCoinGeckoSettings::demo(true, std::option::Option::Some(coingecko_key)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinGecko(coingecko));
|
||||
let coinmarketcap = match ksp_offchain_transport_lib::MarketPriceCoinMarketCapSettings::basic(true, std::option::Option::Some(coinmarketcap_key)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinMarketCap(coinmarketcap));
|
||||
let jupiter = match ksp_offchain_transport_lib::MarketPriceJupiterSettings::free(true, std::option::Option::Some(jupiter_key)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::Jupiter(jupiter));
|
||||
return std::result::Result::Ok(setups);
|
||||
}
|
||||
|
||||
fn assert_live_outcomes(
|
||||
registry: &ksp_offchain_transport_lib::MarketPriceProviderRegistry,
|
||||
outcomes: &[ksp_offchain_transport_lib::MarketPriceRefreshOutcome],
|
||||
) {
|
||||
let mut failures = std::vec::Vec::new();
|
||||
for outcome in outcomes {
|
||||
let provider_id = outcome.provider_id();
|
||||
let observation = match outcome.observation() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
failures.push(std::format!("{}:{:?}", provider_id.as_str(), outcome.state().availability()));
|
||||
continue;
|
||||
},
|
||||
};
|
||||
assert_eq!(observation.provider_id(), provider_id);
|
||||
assert_eq!(observation.pair(), ksp_offchain_transport_lib::MarketPricePair::SolUsd);
|
||||
assert!(observation.price().coefficient() > 0);
|
||||
assert!(observation.received_at() >= observation.request_started_at());
|
||||
let descriptor = match registry.descriptor(provider_id) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
failures.push(std::format!("{}:missing_registry_descriptor", provider_id.as_str()));
|
||||
continue;
|
||||
},
|
||||
};
|
||||
assert_eq!(observation.semantics(), descriptor.semantics());
|
||||
}
|
||||
assert!(failures.is_empty(), "live market-price providers did not all refresh successfully after every provider was attempted: {failures:?}");
|
||||
return;
|
||||
}
|
||||
|
||||
async fn run_live_smoke(setups: std::vec::Vec<ksp_offchain_transport_lib::MarketPriceProviderSetup>) -> ksp_core_lib::Result<()> {
|
||||
let service = match ksp_offchain_transport_lib::MarketPriceService::new(setups) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let registry = service.registry();
|
||||
let outcomes = match service.refresh_all().await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
assert_eq!(outcomes.len(), registry.len());
|
||||
assert_live_outcomes(®istry, outcomes.as_slice());
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
#[ignore = "opt-in live SOL/USD smoke for seven keyless V1 providers; performs external HTTPS requests"]
|
||||
async fn keyless_market_price_providers_live_smoke() -> ksp_core_lib::Result<()> {
|
||||
let setups = match keyless_setups() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return run_live_smoke(setups).await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
#[ignore = "opt-in live SOL/USD smoke for four free keyed V1 modes; reads API keys from stdin in Birdeye/CoinGecko/CoinMarketCap/Jupiter order"]
|
||||
async fn keyed_market_price_providers_live_smoke() -> ksp_core_lib::Result<()> {
|
||||
let setups = match keyed_setups() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return run_live_smoke(setups).await;
|
||||
}
|
||||
Reference in New Issue
Block a user