Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions src/bitcoin_price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,17 @@ impl BitcoinPriceManager {
crate::price::get_bitcoin_price(currency)
}

/// Test-only: seed the in-memory price cache so unit tests in other
/// modules can exercise price-dependent paths (e.g. range-order bond
/// sizing) deterministically without hitting the network. Use a unique
/// `currency` per test to avoid cross-test interference on the shared
/// static.
/// Test-only: seed the price-override map consulted by
/// [`crate::price::get_bitcoin_price`] so unit tests in other modules
/// can exercise price-dependent paths (e.g. range-order bond sizing)
/// deterministically without hitting the network or installing the
/// global `PriceManager`. Use a unique `currency` per test to avoid
/// cross-test interference on the shared map.
#[cfg(test)]
pub(crate) fn set_price_for_test(currency: &str, price: f64) {
BITCOIN_PRICES
crate::price::test_price_overrides()
.write()
.expect("price cache write lock")
.expect("price override write lock")
.insert(currency.to_string(), price);
}
}
Expand Down
24 changes: 24 additions & 0 deletions src/price/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ pub use store::{AggregatedPrice, PriceError, PriceStore};

use mostro_core::error::{MostroError, ServiceError};

/// Test-only per-currency price overrides consulted by
/// [`get_bitcoin_price`] before the global manager. Unit tests in other
/// modules (e.g. range-order bond sizing in `util.rs`) seed this via
/// `BitcoinPriceManager::set_price_for_test` instead of installing the
/// global [`PriceManager`] — its `OnceLock` would leak one test's
/// configuration into every other test in the binary. Tests use a unique
/// currency code each to avoid cross-test interference on the shared map.
#[cfg(test)]
pub(crate) fn test_price_overrides(
) -> &'static std::sync::RwLock<std::collections::HashMap<String, f64>> {
static OVERRIDES: std::sync::OnceLock<
std::sync::RwLock<std::collections::HashMap<String, f64>>,
> = std::sync::OnceLock::new();
OVERRIDES.get_or_init(|| std::sync::RwLock::new(std::collections::HashMap::new()))
}

/// Read a currency's per-BTC price from the global [`PriceManager`].
///
/// This is the Phase 1 entry point for consumers (`util::get_bitcoin_price`
Expand All @@ -36,6 +52,14 @@ use mostro_core::error::{MostroError, ServiceError};
/// legacy code returned when `BITCOIN_PRICES` was empty, so callers behave
/// identically.
pub fn get_bitcoin_price(currency: &str) -> Result<f64, MostroError> {
#[cfg(test)]
if let Some(price) = test_price_overrides()
.read()
.expect("price override read lock")
.get(currency)
{
return Ok(*price);
}
match PriceManager::global() {
Some(m) => m.get_price(currency),
None => Err(MostroError::MostroInternalErr(ServiceError::NoAPIResponse)),
Expand Down
1 change: 1 addition & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1598,6 +1598,7 @@ pub async fn notify_taker_reputation(
#[cfg(test)]
mod tests {
use super::*;
use crate::bitcoin_price::BitcoinPriceManager;
use mostro_core::message::{Message, MessageKind};
use mostro_core::order::Order;
use sqlx::sqlite::SqlitePoolOptions;
Expand Down