v0.2.11-pre.002-fix.003

This commit is contained in:
2026-08-25 20:37:09 +02:00
parent 126e8abfcd
commit 2dbddfe367
15 changed files with 499 additions and 271 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_decimal.rs
// version: 2
// version: 3
#[test]
fn decimal_normalizes_fractional_and_scientific_forms_without_f64() -> ksp_core_lib::Result<()> {
@@ -11,7 +11,7 @@ fn decimal_normalizes_fractional_and_scientific_forms_without_f64() -> ksp_core_
("1e-3", "0.001", 1, 3),
];
for (source, expected, coefficient, scale) in cases {
let value = match crate::PriceDecimal::parse(source) {
let value = match crate::MarketPriceDecimal::parse(source) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -26,34 +26,34 @@ fn decimal_normalizes_fractional_and_scientific_forms_without_f64() -> ksp_core_
fn decimal_rejects_zero_negative_nonfinite_excessive_scale_and_overflow() {
let invalid = ["0", "0.000", "-1", "+1", "NaN", "inf", "1e-19", "1e129", "340282366920938463463374607431768211456", " 1", "1 ", ".1", "1."];
for source in invalid {
let result = crate::PriceDecimal::parse(source);
let result = crate::MarketPriceDecimal::parse(source);
assert!(result.is_err());
if let std::result::Result::Err(error) = result {
assert_eq!(error.code(), crate::ERROR_CODE_PRICE_DECIMAL_INVALID);
assert_eq!(error.code(), crate::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID);
}
}
}
#[test]
fn decimal_serde_is_canonical_string_and_round_trips_exactly() -> ksp_core_lib::Result<()> {
let value = match crate::PriceDecimal::parse("123.4500") {
let value = match crate::MarketPriceDecimal::parse("123.4500") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let encoded = match serde_json::to_string(&value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_PRICE_DECIMAL_INVALID, "test serialization failed"));
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID, "test serialization failed"));
},
};
assert_eq!(encoded, "\"123.45\"");
let decoded: crate::PriceDecimal = match serde_json::from_str(encoded.as_str()) {
let decoded: crate::MarketPriceDecimal = match serde_json::from_str(encoded.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_PRICE_DECIMAL_INVALID, "test deserialization failed"));
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID, "test deserialization failed"));
},
};
assert_eq!(decoded, value);
assert!(serde_json::from_str::<crate::PriceDecimal>("123.45").is_err());
assert!(serde_json::from_str::<crate::MarketPriceDecimal>("123.45").is_err());
return std::result::Result::Ok(());
}

View File

@@ -1,26 +1,26 @@
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_observation.rs
// version: 2
// version: 3
#[test]
fn observation_preserves_pair_exact_price_semantics_timestamps_and_safe_provenance() -> ksp_core_lib::Result<()> {
let provider_id = match crate::PriceProviderId::new("kraken") {
let provider_id = match crate::MarketPriceProviderId::new("kraken") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let price = match crate::PriceDecimal::parse("204.125") {
let price = match crate::MarketPriceDecimal::parse("204.125") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let provenance = match crate::PriceProvenance::new("market=SOL/USD") {
let provenance = match crate::MarketPriceProvenance::new("market=SOL/USD") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let started = crate::PriceTimestamp::from_unix_millis(10_000);
let received = crate::PriceTimestamp::from_unix_millis(10_250);
let observation = match crate::SolUsdPriceObservation::new(
let started = crate::MarketPriceTimestamp::from_unix_millis(10_000);
let received = crate::MarketPriceTimestamp::from_unix_millis(10_250);
let observation = match crate::MarketPriceObservation::new(
provider_id,
price,
crate::PriceSemantics::ExchangeLastTrade,
crate::MarketPriceSemantics::ExchangeLastTrade,
started,
received,
std::option::Option::None,
@@ -29,10 +29,10 @@ fn observation_preserves_pair_exact_price_semantics_timestamps_and_safe_provenan
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert_eq!(observation.pair(), crate::PricePair::SolUsd);
assert_eq!(observation.pair(), crate::MarketPricePair::SolUsd);
assert_eq!(observation.pair().code(), "SOL/USD");
assert_eq!(observation.price(), price);
assert_eq!(observation.semantics(), crate::PriceSemantics::ExchangeLastTrade);
assert_eq!(observation.semantics(), crate::MarketPriceSemantics::ExchangeLastTrade);
assert_eq!(observation.request_started_at(), started);
assert_eq!(observation.received_at(), received);
assert_eq!(observation.provider_timestamp(), std::option::Option::None);
@@ -42,25 +42,25 @@ fn observation_preserves_pair_exact_price_semantics_timestamps_and_safe_provenan
#[test]
fn observation_rejects_reversed_ksp_timestamps_and_unsafe_provenance() -> ksp_core_lib::Result<()> {
assert!(crate::PriceProvenance::new("line\nbreak").is_err());
let provider_id = match crate::PriceProviderId::new("coingecko") {
assert!(crate::MarketPriceProvenance::new("line\nbreak").is_err());
let provider_id = match crate::MarketPriceProviderId::new("coingecko") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let price = match crate::PriceDecimal::parse("200") {
let price = match crate::MarketPriceDecimal::parse("200") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let provenance = match crate::PriceProvenance::new("asset=solana") {
let provenance = match crate::MarketPriceProvenance::new("asset=solana") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let result = crate::SolUsdPriceObservation::new(
let result = crate::MarketPriceObservation::new(
provider_id,
price,
crate::PriceSemantics::AggregatedMarket,
crate::PriceTimestamp::from_unix_millis(2),
crate::PriceTimestamp::from_unix_millis(1),
crate::MarketPriceSemantics::AggregatedMarket,
crate::MarketPriceTimestamp::from_unix_millis(2),
crate::MarketPriceTimestamp::from_unix_millis(1),
std::option::Option::None,
provenance,
);

View File

@@ -1,39 +1,43 @@
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs
// version: 2
// version: 3
#[test]
fn provider_id_is_opaque_bounded_and_stable() -> ksp_core_lib::Result<()> {
let id = match crate::PriceProviderId::new("coingecko-main") {
let id = match crate::MarketPriceProviderId::new("coingecko-main") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert_eq!(id.as_str(), "coingecko-main");
assert_eq!(id.to_string(), "coingecko-main");
for invalid in ["", "CoinGecko", "coin gecko", "coin/gecko", "é"] {
assert!(crate::PriceProviderId::new(invalid).is_err());
assert!(crate::MarketPriceProviderId::new(invalid).is_err());
}
return std::result::Result::Ok(());
}
#[test]
fn provider_descriptor_preserves_semantics_auth_limits_and_informational_quota() -> ksp_core_lib::Result<()> {
let id = match crate::PriceProviderId::new("provider-a") {
let id = match crate::MarketPriceProviderId::new("provider-a") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let rate_limit = match crate::PriceProviderRateLimit::fixed(1, 2, std::option::Option::None, crate::PriceProviderRateLimitScope::Ip) {
let rate_limit = match crate::MarketPriceProviderRateLimit::fixed(1, 2, std::option::Option::None, crate::MarketPriceProviderRateLimitScope::Ip) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let quota = match crate::PriceProviderLongTermQuota::new(10_000, crate::PriceProviderQuotaPeriod::Month, crate::PriceProviderQuotaUnit::Credits) {
let quota = match crate::MarketPriceProviderLongTermQuota::new(
10_000,
crate::MarketPriceProviderQuotaPeriod::Month,
crate::MarketPriceProviderQuotaUnit::Credits,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let descriptor = match crate::PriceProviderDescriptor::new(
let descriptor = match crate::MarketPriceProviderDescriptor::new(
id,
"Provider A",
crate::PriceSemantics::AggregatedMarket,
crate::PriceProviderAuthMode::OptionalApiKey,
crate::MarketPriceSemantics::AggregatedMarket,
crate::MarketPriceProviderAuthMode::OptionalApiKey,
rate_limit,
std::option::Option::Some(quota),
true,
@@ -42,8 +46,8 @@ fn provider_descriptor_preserves_semantics_auth_limits_and_informational_quota()
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert_eq!(descriptor.display_name(), "Provider A");
assert_eq!(descriptor.semantics(), crate::PriceSemantics::AggregatedMarket);
assert_eq!(descriptor.auth_mode(), crate::PriceProviderAuthMode::OptionalApiKey);
assert_eq!(descriptor.semantics(), crate::MarketPriceSemantics::AggregatedMarket);
assert_eq!(descriptor.auth_mode(), crate::MarketPriceProviderAuthMode::OptionalApiKey);
assert_eq!(descriptor.rate_limit(), rate_limit);
assert_eq!(descriptor.long_term_quota(), std::option::Option::Some(quota));
assert!(descriptor.supports_sol_usd());
@@ -52,12 +56,12 @@ fn provider_descriptor_preserves_semantics_auth_limits_and_informational_quota()
#[test]
fn provider_limit_descriptors_reject_zero_and_model_dynamic_scope() {
assert!(crate::PriceProviderRateLimit::fixed(0, 1, std::option::Option::None, crate::PriceProviderRateLimitScope::Ip).is_err());
assert!(crate::PriceProviderRateLimit::fixed(1, 0, std::option::Option::None, crate::PriceProviderRateLimitScope::Ip).is_err());
assert!(crate::PriceProviderRateLimit::fixed(1, 1, std::option::Option::Some(0), crate::PriceProviderRateLimitScope::Ip).is_err());
let dynamic = crate::PriceProviderRateLimit::dynamic(crate::PriceProviderRateLimitScope::Ip);
assert_eq!(dynamic.kind(), crate::PriceProviderRateLimitKind::Dynamic);
assert_eq!(dynamic.scope(), crate::PriceProviderRateLimitScope::Ip);
assert!(crate::MarketPriceProviderRateLimit::fixed(0, 1, std::option::Option::None, crate::MarketPriceProviderRateLimitScope::Ip).is_err());
assert!(crate::MarketPriceProviderRateLimit::fixed(1, 0, std::option::Option::None, crate::MarketPriceProviderRateLimitScope::Ip).is_err());
assert!(crate::MarketPriceProviderRateLimit::fixed(1, 1, std::option::Option::Some(0), crate::MarketPriceProviderRateLimitScope::Ip).is_err());
let dynamic = crate::MarketPriceProviderRateLimit::dynamic(crate::MarketPriceProviderRateLimitScope::Ip);
assert_eq!(dynamic.kind(), crate::MarketPriceProviderRateLimitKind::Dynamic);
assert_eq!(dynamic.scope(), crate::MarketPriceProviderRateLimitScope::Ip);
assert_eq!(dynamic.requests(), std::option::Option::None);
assert_eq!(dynamic.window_seconds(), std::option::Option::None);
assert_eq!(dynamic.burst(), std::option::Option::None);
@@ -65,13 +69,13 @@ fn provider_limit_descriptors_reject_zero_and_model_dynamic_scope() {
#[test]
fn provider_availability_keeps_cooldown_and_outage_distinct() -> ksp_core_lib::Result<()> {
let id = match crate::PriceProviderId::new("jupiter") {
let id = match crate::MarketPriceProviderId::new("jupiter") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let retry_at = crate::PriceTimestamp::from_unix_millis(1_777_777_777_000);
let state = crate::PriceProviderState::new(id, crate::PriceProviderAvailability::CoolingDown { retry_at });
assert_eq!(state.availability(), crate::PriceProviderAvailability::CoolingDown { retry_at });
assert_ne!(state.availability(), crate::PriceProviderAvailability::TemporarilyUnavailable { retry_at: std::option::Option::Some(retry_at) });
let retry_at = crate::MarketPriceTimestamp::from_unix_millis(1_777_777_777_000);
let state = crate::MarketPriceProviderState::new(id, crate::MarketPriceProviderAvailability::CoolingDown { retry_at });
assert_eq!(state.availability(), crate::MarketPriceProviderAvailability::CoolingDown { retry_at });
assert_ne!(state.availability(), crate::MarketPriceProviderAvailability::TemporarilyUnavailable { retry_at: std::option::Option::Some(retry_at) });
return std::result::Result::Ok(());
}

View File

@@ -1,19 +1,19 @@
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_settings.rs
// version: 2
// version: 3
#[test]
fn common_settings_contain_only_generic_identity_and_enablement() -> ksp_core_lib::Result<()> {
let provider_id = match crate::PriceProviderId::new("coinpaprika") {
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 settings = crate::PriceProviderCommonSettings::new(provider_id, true);
let settings = crate::MarketPriceProviderCommonSettings::new(provider_id, true);
assert!(settings.enabled());
assert_eq!(settings.provider_id().as_str(), "coinpaprika");
let serialized = match serde_json::to_value(&settings) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_PROVIDER_SETTINGS_INVALID, "test serialization failed"));
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_PROVIDER_SETTINGS_INVALID, "test serialization failed"));
},
};
assert!(serialized.get("api_key").is_none());