// file: crates/ksp-app-solprices-desk/unit_tests/market_price_runtime.rs // version: 2 fn descriptor() -> std::option::Option { let provider_id = ksp_offchain_transport_lib::MarketPriceProviderId::new("test-provider"); let provider_id = match provider_id { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::option::Option::None, }; let rate_limit = ksp_offchain_transport_lib::MarketPriceProviderRateLimit::fixed( 10, 60, std::option::Option::None, ksp_offchain_transport_lib::MarketPriceProviderRateLimitScope::Unspecified, ); let rate_limit = match rate_limit { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::option::Option::None, }; let descriptor = ksp_offchain_transport_lib::MarketPriceProviderDescriptor::new( provider_id, "Test Provider", ksp_offchain_transport_lib::MarketPriceSemantics::AggregatedMarket, ksp_offchain_transport_lib::MarketPriceProviderAuthMode::None, rate_limit, std::option::Option::None, true, ); return match descriptor { std::result::Result::Ok(value) => std::option::Option::Some(value), std::result::Result::Err(_) => std::option::Option::None, }; } #[test] fn generic_codes_cover_current_provider_neutral_enums() { assert_eq!(super::auth_mode_code(ksp_offchain_transport_lib::MarketPriceProviderAuthMode::None), "none"); assert_eq!(super::auth_mode_code(ksp_offchain_transport_lib::MarketPriceProviderAuthMode::OptionalApiKey), "optional_api_key"); assert_eq!(super::auth_mode_code(ksp_offchain_transport_lib::MarketPriceProviderAuthMode::RequiredApiKey), "required_api_key"); assert_eq!( super::availability_code(ksp_offchain_transport_lib::MarketPriceProviderAvailability::AuthenticationUnavailable), "authentication_unavailable" ); assert_eq!( super::availability_code(ksp_offchain_transport_lib::MarketPriceProviderAvailability::CoolingDown { retry_at: ksp_offchain_transport_lib::MarketPriceTimestamp::from_unix_millis(123), }), "cooling_down" ); assert_eq!(super::availability_code(ksp_offchain_transport_lib::MarketPriceProviderAvailability::Disabled), "disabled"); assert_eq!(super::availability_code(ksp_offchain_transport_lib::MarketPriceProviderAvailability::Misconfigured), "misconfigured"); assert_eq!(super::availability_code(ksp_offchain_transport_lib::MarketPriceProviderAvailability::QuotaUnavailable), "quota_unavailable"); assert_eq!(super::availability_code(ksp_offchain_transport_lib::MarketPriceProviderAvailability::Ready), "ready"); assert_eq!( super::availability_code(ksp_offchain_transport_lib::MarketPriceProviderAvailability::TemporarilyUnavailable { retry_at: std::option::Option::None }), "temporarily_unavailable" ); assert_eq!(super::semantics_code(ksp_offchain_transport_lib::MarketPriceSemantics::AggregatedMarket), "aggregated_market"); assert_eq!(super::semantics_code(ksp_offchain_transport_lib::MarketPriceSemantics::DexPairUsd), "dex_pair_usd"); assert_eq!(super::semantics_code(ksp_offchain_transport_lib::MarketPriceSemantics::ExchangeLastTrade), "exchange_last_trade"); assert_eq!(super::semantics_code(ksp_offchain_transport_lib::MarketPriceSemantics::SolanaHeuristic), "solana_heuristic"); assert_eq!(super::semantics_code(ksp_offchain_transport_lib::MarketPriceSemantics::SolanaSpot), "solana_spot"); } #[test] fn initial_registry_row_contains_no_observation_or_loading_state() { let descriptor = descriptor(); assert!(descriptor.is_some()); let descriptor = match descriptor { std::option::Option::Some(value) => value, std::option::Option::None => return, }; let entry = ksp_offchain_transport_lib::MarketPriceProviderRegistryEntry::new(descriptor, ksp_offchain_transport_lib::MarketPriceProviderAvailability::Ready); let presentation = super::MarketPricePresentationEntry { loading: false, observation: std::option::Option::None }; let row = super::project_registry_entry(&entry, &presentation); assert_eq!(row.provider_id, "test-provider"); assert_eq!(row.display_name, "Test Provider"); assert_eq!(row.pair, "SOL/USD"); assert_eq!(row.auth_mode, "none"); assert_eq!(row.availability, "ready"); assert_eq!(row.semantics, "aggregated_market"); assert!(!row.loading); assert!(!row.refreshed); assert_eq!(row.price, std::option::Option::None); assert_eq!(row.provider_timestamp_unix_millis, std::option::Option::None); assert_eq!(row.received_at_unix_millis, std::option::Option::None); assert_eq!(row.retry_at_unix_millis, std::option::Option::None); } #[test] fn observation_projection_keeps_exact_decimal_and_distinct_timestamps() { let descriptor = descriptor(); assert!(descriptor.is_some()); let descriptor = match descriptor { std::option::Option::Some(value) => value, std::option::Option::None => return, }; let provider_id = descriptor.id().clone(); let entry = ksp_offchain_transport_lib::MarketPriceProviderRegistryEntry::new( descriptor, ksp_offchain_transport_lib::MarketPriceProviderAvailability::CoolingDown { retry_at: ksp_offchain_transport_lib::MarketPriceTimestamp::from_unix_millis(400), }, ); let price = ksp_offchain_transport_lib::MarketPriceDecimal::parse("123.4500"); assert!(price.is_ok()); let price = match price { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return, }; let provenance = ksp_offchain_transport_lib::MarketPriceProvenance::new("unit-test"); assert!(provenance.is_ok()); let provenance = match provenance { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return, }; let observation = ksp_offchain_transport_lib::MarketPriceObservation::new( provider_id, price, ksp_offchain_transport_lib::MarketPriceSemantics::AggregatedMarket, ksp_offchain_transport_lib::MarketPriceTimestamp::from_unix_millis(100), ksp_offchain_transport_lib::MarketPriceTimestamp::from_unix_millis(300), std::option::Option::Some(ksp_offchain_transport_lib::MarketPriceTimestamp::from_unix_millis(200)), provenance, ); assert!(observation.is_ok()); let observation = match observation { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return, }; let presentation = super::MarketPricePresentationEntry { loading: true, observation: std::option::Option::Some(observation) }; let row = super::project_registry_entry(&entry, &presentation); assert_eq!(row.price.as_deref(), std::option::Option::Some("123.45")); assert_eq!(row.provider_timestamp_unix_millis.as_deref(), std::option::Option::Some("200")); assert_eq!(row.received_at_unix_millis.as_deref(), std::option::Option::Some("300")); assert_eq!(row.retry_at_unix_millis.as_deref(), std::option::Option::Some("400")); assert!(row.loading); assert!(row.refreshed); } #[tokio::test] async fn disabled_refresh_clears_loading_without_inventing_observation() -> ksp_core_lib::Result<()> { let settings = match ksp_offchain_transport_lib::MarketPriceCoinPaprikaSettings::new(false) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let service = match ksp_offchain_transport_lib::MarketPriceService::new(std::vec![ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinPaprika(settings),]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let provider_id = match ksp_offchain_transport_lib::MarketPriceProviderId::new("coinpaprika") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let mut entries = std::collections::BTreeMap::new(); entries.insert(provider_id.as_str().to_owned(), super::MarketPricePresentationEntry { loading: true, observation: std::option::Option::None }); let mut presentation = super::MarketPricePresentationState { entries }; let outcome = match service.refresh(&provider_id).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; assert!(!outcome.refreshed()); let applied = super::apply_outcome_to_state(&mut presentation, &outcome); if let std::result::Result::Err(error) = applied { return std::result::Result::Err(error); } let entry = presentation.entries.get(provider_id.as_str()); assert!(entry.is_some()); if let std::option::Option::Some(entry) = entry { assert!(!entry.loading); assert!(entry.observation.is_none()); } return std::result::Result::Ok(()); } #[tokio::test] async fn non_refresh_outcome_preserves_last_successful_observation() -> ksp_core_lib::Result<()> { let settings = match ksp_offchain_transport_lib::MarketPriceCoinPaprikaSettings::new(false) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let service = match ksp_offchain_transport_lib::MarketPriceService::new(std::vec![ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinPaprika(settings),]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let provider_id = match ksp_offchain_transport_lib::MarketPriceProviderId::new("coinpaprika") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let price = match ksp_offchain_transport_lib::MarketPriceDecimal::parse("101.2500") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let provenance = match ksp_offchain_transport_lib::MarketPriceProvenance::new("unit-test") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let observation = match ksp_offchain_transport_lib::MarketPriceObservation::new( provider_id.clone(), price, ksp_offchain_transport_lib::MarketPriceSemantics::AggregatedMarket, ksp_offchain_transport_lib::MarketPriceTimestamp::from_unix_millis(10), ksp_offchain_transport_lib::MarketPriceTimestamp::from_unix_millis(30), std::option::Option::Some(ksp_offchain_transport_lib::MarketPriceTimestamp::from_unix_millis(20)), provenance, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let mut entries = std::collections::BTreeMap::new(); entries.insert(provider_id.as_str().to_owned(), super::MarketPricePresentationEntry { loading: true, observation: std::option::Option::Some(observation) }); let mut presentation = super::MarketPricePresentationState { entries }; let outcome = match service.refresh(&provider_id).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; assert!(!outcome.refreshed()); let applied = super::apply_outcome_to_state(&mut presentation, &outcome); if let std::result::Result::Err(error) = applied { return std::result::Result::Err(error); } let entry = presentation.entries.get(provider_id.as_str()); assert!(entry.is_some()); if let std::option::Option::Some(entry) = entry { assert!(!entry.loading); assert_eq!(entry.observation.as_ref().map(|value| return value.price().to_canonical_string()).as_deref(), std::option::Option::Some("101.25")); } return std::result::Result::Ok(()); }