From c80db442d013591de75094b9203ac43f1ee65134 Mon Sep 17 00:00:00 2001 From: grunch Date: Fri, 12 Jun 2026 10:46:55 -0300 Subject: [PATCH] fix(price): repair test-only price seeding broken by #753/#770 merge skew MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #753 removed the BITCOIN_PRICES static when it migrated price reads to the PriceManager, while PR #770 (merged independently) added BitcoinPriceManager::set_price_for_test writing to that static — the combination doesn't compile under cargo test on main. Re-point the test seam at a small cfg(test) override map consulted by price::get_bitcoin_price before the global manager, so unit tests keep seeding deterministic prices without installing the global PriceManager (whose OnceLock would leak one test's configuration into the rest of the binary). Co-Authored-By: Claude Fable 5 --- src/bitcoin_price.rs | 15 ++++++++------- src/price/mod.rs | 24 ++++++++++++++++++++++++ src/util.rs | 1 + 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/bitcoin_price.rs b/src/bitcoin_price.rs index d8236e2d..200e25ce 100644 --- a/src/bitcoin_price.rs +++ b/src/bitcoin_price.rs @@ -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); } } diff --git a/src/price/mod.rs b/src/price/mod.rs index 3cde5fa4..4c6bf8eb 100644 --- a/src/price/mod.rs +++ b/src/price/mod.rs @@ -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> { + static OVERRIDES: std::sync::OnceLock< + std::sync::RwLock>, + > = 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` @@ -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 { + #[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)), diff --git a/src/util.rs b/src/util.rs index e7a43c9b..36d1ec89 100644 --- a/src/util.rs +++ b/src/util.rs @@ -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;