v0.2.11-pre.008
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
#[test]
|
||||
fn provider_id_is_opaque_bounded_and_stable() -> ksp_core_lib::Result<()> {
|
||||
@@ -130,3 +130,16 @@ fn provider_availability_keeps_cooldown_and_outage_distinct() -> ksp_core_lib::R
|
||||
assert_ne!(state.availability(), crate::MarketPriceProviderAvailability::TemporarilyUnavailable { retry_at: std::option::Option::Some(retry_at) });
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_availability_eligibility_at_respects_retry_deadlines_without_consumer_scheduling() {
|
||||
let now = crate::MarketPriceTimestamp::from_unix_millis(100);
|
||||
let future = crate::MarketPriceTimestamp::from_unix_millis(200);
|
||||
let past = crate::MarketPriceTimestamp::from_unix_millis(50);
|
||||
assert!(crate::MarketPriceProviderAvailability::Ready.is_refresh_eligible_at(now));
|
||||
assert!(!crate::MarketPriceProviderAvailability::Disabled.is_refresh_eligible_at(now));
|
||||
assert!(!crate::MarketPriceProviderAvailability::CoolingDown { retry_at: future }.is_refresh_eligible_at(now));
|
||||
assert!(crate::MarketPriceProviderAvailability::CoolingDown { retry_at: past }.is_refresh_eligible_at(now));
|
||||
assert!(crate::MarketPriceProviderAvailability::TemporarilyUnavailable { retry_at: std::option::Option::None }.is_refresh_eligible_at(now));
|
||||
assert!(crate::MarketPriceProviderAvailability::TemporarilyUnavailable { retry_at: std::option::Option::Some(past) }.is_refresh_eligible_at(now));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_service.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn service_registry_orders_all_eight_v1_setups_and_keeps_runtime_projection_generic() -> ksp_core_lib::Result<()> {
|
||||
let setups = match all_disabled_setups() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let service = match crate::MarketPriceService::new(setups) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let registry = service.registry();
|
||||
assert_eq!(registry.len(), 8);
|
||||
let ids = registry.entries().iter().map(|entry| return entry.descriptor().id().as_str()).collect::<std::vec::Vec<_>>();
|
||||
assert_eq!(ids, std::vec!["birdeye", "coinbase_exchange", "coingecko", "coinmarketcap", "coinpaprika", "dexscreener", "jupiter", "kraken",]);
|
||||
assert!(registry.entries().iter().all(|entry| return entry.state().availability() == crate::MarketPriceProviderAvailability::Disabled));
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn refresh_all_projects_disabled_providers_without_network_dispatch() -> ksp_core_lib::Result<()> {
|
||||
let setups = match all_disabled_setups() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let service = match crate::MarketPriceService::new(setups) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
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(), 8);
|
||||
assert!(outcomes.iter().all(|outcome| return !outcome.refreshed()));
|
||||
assert!(outcomes.iter().all(|outcome| return outcome.state().availability() == crate::MarketPriceProviderAvailability::Disabled));
|
||||
let ids = outcomes.iter().map(|outcome| return outcome.provider_id().as_str()).collect::<std::vec::Vec<_>>();
|
||||
assert_eq!(ids, std::vec!["birdeye", "coinbase_exchange", "coingecko", "coinmarketcap", "coinpaprika", "dexscreener", "jupiter", "kraken",]);
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn refresh_many_rejects_duplicates_and_unknown_provider_before_dispatch() -> ksp_core_lib::Result<()> {
|
||||
let settings = match crate::MarketPriceCoinPaprikaSettings::new(false) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let service = match crate::MarketPriceService::new(std::vec![crate::MarketPriceProviderSetup::CoinPaprika(settings)]) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let provider_id = match crate::MarketPriceProviderId::new("coinpaprika") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let duplicate = service.refresh_many(&[provider_id.clone(), provider_id]).await;
|
||||
let duplicate_error = match duplicate {
|
||||
std::result::Result::Err(error) => error,
|
||||
std::result::Result::Ok(_) => return std::result::Result::Err(test_error("duplicate")),
|
||||
};
|
||||
assert_eq!(duplicate_error.code(), crate::ERROR_CODE_MARKET_PRICE_REFRESH_INVALID);
|
||||
let unknown_id = match crate::MarketPriceProviderId::new("unknown") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let unknown = service.refresh(&unknown_id).await;
|
||||
let unknown_error = match unknown {
|
||||
std::result::Result::Err(error) => error,
|
||||
std::result::Result::Ok(_) => return std::result::Result::Err(test_error("unknown")),
|
||||
};
|
||||
assert_eq!(unknown_error.code(), crate::ERROR_CODE_MARKET_PRICE_PROVIDER_NOT_FOUND);
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refresh_error_classification_is_provider_neutral_and_preserves_known_retry_time() {
|
||||
let auth = ksp_core_lib::Error::new(crate::ERROR_CODE_HTTP_ACCESS_DENIED, "test");
|
||||
assert_eq!(
|
||||
super::availability_from_error(&auth, crate::MarketPriceProviderAuthMode::RequiredApiKey),
|
||||
crate::MarketPriceProviderAvailability::AuthenticationUnavailable
|
||||
);
|
||||
assert!(matches!(
|
||||
super::availability_from_error(&auth, crate::MarketPriceProviderAuthMode::None),
|
||||
crate::MarketPriceProviderAvailability::TemporarilyUnavailable { retry_at: std::option::Option::None }
|
||||
));
|
||||
let rate = ksp_core_lib::Error::new(crate::ERROR_CODE_HTTP_RATE_LIMITED, "test").with_context("retry_after_seconds", "2");
|
||||
assert!(matches!(
|
||||
super::availability_from_error(&rate, crate::MarketPriceProviderAuthMode::None),
|
||||
crate::MarketPriceProviderAvailability::CoolingDown { .. }
|
||||
));
|
||||
let temporary = ksp_core_lib::Error::new(crate::ERROR_CODE_HTTP_TEMPORARY_FAILURE, "test").with_context("retry_after_seconds", "3");
|
||||
let availability = super::availability_from_error(&temporary, crate::MarketPriceProviderAuthMode::None);
|
||||
assert!(matches!(availability, crate::MarketPriceProviderAvailability::TemporarilyUnavailable { retry_at: std::option::Option::Some(_) }));
|
||||
let local = ksp_core_lib::Error::new(crate::ERROR_CODE_HTTP_REQUEST_INVALID, "test");
|
||||
assert_eq!(super::availability_from_error(&local, crate::MarketPriceProviderAuthMode::None), crate::MarketPriceProviderAvailability::Misconfigured);
|
||||
}
|
||||
|
||||
fn all_disabled_setups() -> ksp_core_lib::Result<std::vec::Vec<crate::MarketPriceProviderSetup>> {
|
||||
let birdeye = match crate::MarketPriceBirdeyeSettings::new(false, std::option::Option::None) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let coinbase = match crate::MarketPriceCoinbaseExchangeSettings::new(false) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let coingecko = match crate::MarketPriceCoinGeckoSettings::keyless(false) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let coinmarketcap = match crate::MarketPriceCoinMarketCapSettings::keyless(false) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let coinpaprika = match crate::MarketPriceCoinPaprikaSettings::new(false) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let dexscreener = match crate::MarketPriceDexScreenerSettings::new(false, "Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let jupiter = match crate::MarketPriceJupiterSettings::keyless(false) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let kraken = match crate::MarketPriceKrakenSettings::new(false) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(std::vec![
|
||||
crate::MarketPriceProviderSetup::Birdeye(birdeye),
|
||||
crate::MarketPriceProviderSetup::CoinbaseExchange(coinbase),
|
||||
crate::MarketPriceProviderSetup::CoinGecko(coingecko),
|
||||
crate::MarketPriceProviderSetup::CoinMarketCap(coinmarketcap),
|
||||
crate::MarketPriceProviderSetup::CoinPaprika(coinpaprika),
|
||||
crate::MarketPriceProviderSetup::DexScreener(dexscreener),
|
||||
crate::MarketPriceProviderSetup::Jupiter(jupiter),
|
||||
crate::MarketPriceProviderSetup::Kraken(kraken),
|
||||
]);
|
||||
}
|
||||
|
||||
fn test_error(field: &'static str) -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_REFRESH_INVALID, "Market-price service test expectation failed")
|
||||
.with_context("field", field);
|
||||
}
|
||||
Reference in New Issue
Block a user