#![allow(clippy::disallowed_methods)]
use ccxt_core::ExchangeConfig;
use ccxt_core::exchange::{ArcExchange, BoxedExchange, Exchange};
use ccxt_core::types::default_type::DefaultType;
use ccxt_exchanges::binance::{Binance, BinanceBuilder};
use std::sync::Arc;
use std::time::Duration;
#[test]
fn test_binance_as_boxed_exchange() {
let config = ExchangeConfig::default();
let binance = Binance::new(config).expect("Should create Binance instance");
let exchange: BoxedExchange = Box::new(binance);
assert_eq!(exchange.id(), "binance");
assert_eq!(exchange.name(), "Binance");
assert_eq!(exchange.version(), "v3");
assert!(exchange.certified());
assert!(exchange.has_websocket());
}
#[test]
fn test_binance_as_arc_exchange() {
let binance = BinanceBuilder::new()
.build()
.expect("Should create Binance instance");
let exchange: ArcExchange = Arc::new(binance);
assert_eq!(exchange.id(), "binance");
assert_eq!(exchange.name(), "Binance");
assert!(exchange.certified());
let exchange_clone = Arc::clone(&exchange);
assert_eq!(exchange_clone.id(), "binance");
assert_eq!(Arc::strong_count(&exchange), 2);
}
#[test]
fn test_trait_object_metadata_methods() {
let binance = Binance::new(ExchangeConfig::default()).expect("Should create Binance instance");
let exchange: &dyn Exchange = &binance;
assert_eq!(exchange.id(), "binance");
assert_eq!(exchange.name(), "Binance");
assert_eq!(exchange.version(), "v3");
assert!(exchange.certified());
assert!(exchange.has_websocket());
assert_eq!(exchange.rate_limit(), 50);
let timeframes = exchange.timeframes();
assert!(!timeframes.is_empty());
assert!(timeframes.len() >= 10); }
#[test]
fn test_trait_object_capabilities() {
let binance = Binance::new(ExchangeConfig::default()).expect("Should create Binance instance");
let exchange: &dyn Exchange = &binance;
let caps = exchange.capabilities();
assert!(caps.fetch_markets());
assert!(caps.fetch_ticker());
assert!(caps.fetch_tickers());
assert!(caps.fetch_order_book());
assert!(caps.fetch_trades());
assert!(caps.fetch_ohlcv());
assert!(caps.create_order());
assert!(caps.cancel_order());
assert!(caps.fetch_balance());
assert!(caps.websocket());
assert!(!caps.edit_order());
}
#[test]
fn test_trait_object_in_generic_function() {
fn get_exchange_info(exchange: &dyn Exchange) -> String {
format!(
"{} ({}) v{} - certified: {}",
exchange.name(),
exchange.id(),
exchange.version(),
exchange.certified()
)
}
let binance = Binance::new(ExchangeConfig::default()).expect("Should create Binance instance");
let info = get_exchange_info(&binance);
assert!(info.contains("Binance"));
assert!(info.contains("binance"));
assert!(info.contains("v3"));
assert!(info.contains("certified: true"));
}
#[test]
fn test_boxed_exchange_in_collection() {
let binance = Binance::new(ExchangeConfig::default()).expect("Should create Binance instance");
let exchanges: Vec<BoxedExchange> = vec![Box::new(binance)];
assert_eq!(exchanges.len(), 1);
assert_eq!(exchanges[0].id(), "binance");
}
#[test]
fn test_arc_exchange_thread_safety() {
let binance = Binance::new(ExchangeConfig::default()).expect("Should create Binance instance");
let exchange: ArcExchange = Arc::new(binance);
let handles: Vec<_> = (0..4)
.map(|_| {
let ex = Arc::clone(&exchange);
std::thread::spawn(move || {
(ex.id().to_string(), ex.name().to_string(), ex.certified())
})
})
.collect();
for handle in handles {
let (id, name, certified) = handle.join().expect("Thread should not panic");
assert_eq!(id, "binance");
assert_eq!(name, "Binance");
assert!(certified);
}
}
#[tokio::test]
async fn test_trait_object_helper_methods() {
let binance = Binance::new(ExchangeConfig::default()).expect("Should create Binance instance");
let exchange: &dyn Exchange = &binance;
assert!(!exchange.is_symbol_active("BTC/USDT").await);
let markets = exchange.markets().await;
assert!(markets.is_empty());
}
#[test]
fn test_capability_check_before_method_call() {
fn check_and_describe_capabilities(exchange: &dyn Exchange) -> Vec<String> {
let caps = exchange.capabilities();
let mut supported = Vec::new();
if caps.fetch_ticker() {
supported.push("fetch_ticker".to_string());
}
if caps.create_order() {
supported.push("create_order".to_string());
}
if caps.websocket() {
supported.push("websocket".to_string());
}
if caps.edit_order() {
supported.push("edit_order".to_string());
}
supported
}
let binance = Binance::new(ExchangeConfig::default()).expect("Should create Binance instance");
let supported = check_and_describe_capabilities(&binance);
assert!(supported.contains(&"fetch_ticker".to_string()));
assert!(supported.contains(&"create_order".to_string()));
assert!(supported.contains(&"websocket".to_string()));
assert!(!supported.contains(&"edit_order".to_string())); }
#[test]
fn test_multiple_exchanges_unified_interface() {
let binance_spot = BinanceBuilder::new()
.default_type(DefaultType::Spot)
.build()
.expect("Should create spot Binance");
let binance_futures = BinanceBuilder::new()
.default_type(DefaultType::Swap) .build()
.expect("Should create futures Binance");
let exchanges: Vec<BoxedExchange> = vec![Box::new(binance_spot), Box::new(binance_futures)];
for exchange in &exchanges {
assert_eq!(exchange.id(), "binance");
assert!(exchange.capabilities().fetch_ticker());
assert!(exchange.capabilities().websocket());
}
assert_eq!(exchanges.len(), 2);
}
#[test]
fn test_exchange_agnostic_function() {
fn summarize_exchange(exchange: &dyn Exchange) -> ExchangeSummary {
let caps = exchange.capabilities();
ExchangeSummary {
id: exchange.id().to_string(),
name: exchange.name().to_string(),
version: exchange.version().to_string(),
certified: exchange.certified(),
has_websocket: exchange.has_websocket(),
can_trade: caps.create_order() && caps.cancel_order(),
can_fetch_market_data: caps.fetch_ticker() && caps.fetch_order_book(),
timeframe_count: exchange.timeframes().len(),
}
}
#[derive(Debug)]
struct ExchangeSummary {
id: String,
name: String,
version: String,
certified: bool,
has_websocket: bool,
can_trade: bool,
can_fetch_market_data: bool,
timeframe_count: usize,
}
let binance = Binance::new(ExchangeConfig::default()).expect("Should create Binance instance");
let summary = summarize_exchange(&binance);
assert_eq!(summary.id, "binance");
assert_eq!(summary.name, "Binance");
assert_eq!(summary.version, "v3");
assert!(summary.certified);
assert!(summary.has_websocket);
assert!(summary.can_trade);
assert!(summary.can_fetch_market_data);
assert!(summary.timeframe_count > 0);
}
#[test]
fn test_exchange_registry_pattern() {
use std::collections::HashMap;
struct ExchangeRegistry {
exchanges: HashMap<String, BoxedExchange>,
}
impl ExchangeRegistry {
fn new() -> Self {
Self {
exchanges: HashMap::new(),
}
}
fn register(&mut self, exchange: BoxedExchange) {
let id = exchange.id().to_string();
self.exchanges.insert(id, exchange);
}
fn get(&self, id: &str) -> Option<&dyn Exchange> {
self.exchanges.get(id).map(|e| e.as_ref())
}
fn list_ids(&self) -> Vec<&str> {
self.exchanges.keys().map(|s| s.as_str()).collect()
}
fn count(&self) -> usize {
self.exchanges.len()
}
}
let mut registry = ExchangeRegistry::new();
let binance = Binance::new(ExchangeConfig::default()).expect("Should create Binance");
registry.register(Box::new(binance));
assert_eq!(registry.count(), 1);
assert!(registry.list_ids().contains(&"binance"));
let exchange = registry.get("binance").expect("Should find binance");
assert_eq!(exchange.name(), "Binance");
assert!(exchange.certified());
}
#[test]
fn test_compare_exchange_capabilities() {
fn compare_capabilities(exchanges: &[&dyn Exchange]) -> CapabilityComparison {
let mut comparison = CapabilityComparison {
all_support_ticker: true,
all_support_websocket: true,
all_certified: true,
exchange_count: exchanges.len(),
};
for exchange in exchanges {
let caps = exchange.capabilities();
comparison.all_support_ticker &= caps.fetch_ticker();
comparison.all_support_websocket &= caps.websocket();
comparison.all_certified &= exchange.certified();
}
comparison
}
#[derive(Debug)]
struct CapabilityComparison {
all_support_ticker: bool,
all_support_websocket: bool,
all_certified: bool,
exchange_count: usize,
}
let binance1 = Binance::new(ExchangeConfig::default()).expect("Should create Binance 1");
let binance2 = Binance::new(ExchangeConfig::default()).expect("Should create Binance 2");
let exchanges: Vec<&dyn Exchange> = vec![&binance1, &binance2];
let comparison = compare_capabilities(&exchanges);
assert!(comparison.all_support_ticker);
assert!(comparison.all_support_websocket);
assert!(comparison.all_certified);
assert_eq!(comparison.exchange_count, 2);
}
#[tokio::test]
async fn test_async_trait_object_compilation() {
async fn _fetch_ticker_if_supported(
exchange: &dyn Exchange,
symbol: &str,
) -> Option<ccxt_core::Result<ccxt_core::Ticker>> {
if exchange.capabilities().fetch_ticker() {
Some(exchange.fetch_ticker(symbol).await)
} else {
None
}
}
let binance = Binance::new(ExchangeConfig::default()).expect("Should create Binance");
let _exchange: &dyn Exchange = &binance;
assert!(binance.capabilities().fetch_ticker());
}
#[test]
fn test_trait_object_with_various_configs() {
let sandbox_binance = BinanceBuilder::new()
.sandbox(true)
.build()
.expect("Should create sandbox Binance");
let exchange: &dyn Exchange = &sandbox_binance;
assert_eq!(exchange.id(), "binance");
let custom_timeout_binance = BinanceBuilder::new()
.timeout(Duration::from_secs(60))
.build()
.expect("Should create Binance with custom timeout");
let exchange: &dyn Exchange = &custom_timeout_binance;
assert_eq!(exchange.id(), "binance");
let auth_binance = BinanceBuilder::new()
.api_key("test-key")
.secret("test-secret")
.build()
.expect("Should create Binance with credentials");
let exchange: &dyn Exchange = &auth_binance;
assert_eq!(exchange.id(), "binance");
assert!(exchange.capabilities().fetch_balance()); }
#[test]
fn test_exchange_capabilities_methods() {
let binance = Binance::new(ExchangeConfig::default()).expect("Should create Binance");
let caps = binance.capabilities();
assert!(caps.has("fetchTicker"));
assert!(caps.has("fetchOrderBook"));
assert!(caps.has("createOrder"));
assert!(caps.has("websocket"));
assert!(!caps.has("editOrder")); assert!(!caps.has("unknownCapability"));
let supported = caps.supported_capabilities();
assert!(supported.contains(&"fetchTicker"));
assert!(supported.contains(&"createOrder"));
assert!(supported.contains(&"websocket"));
}