use ostium_rust_sdk::{Network, OpenPositionParams, OstiumClient, PositionSide};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use std::time::Duration;
use tokio::time::sleep;
async fn create_test_client() -> Result<OstiumClient, Box<dyn std::error::Error>> {
Ok(OstiumClient::new(Network::Testnet).await?)
}
#[tokio::test]
async fn test_minimum_position_size_validation() {
let client = create_test_client().await.unwrap();
sleep(Duration::from_millis(500)).await;
let min_size_result = client.get_minimum_position_size("BTC/USD").await;
match min_size_result {
Ok(min_size) => {
println!("✅ Minimum size calculation working: {} BTC", min_size);
assert!(min_size > Decimal::ZERO, "Minimum size should be positive");
let validation_result = client
.validate_trading_constraints(
"BTC/USD",
PositionSide::Long,
dec!(0.000001),
dec!(10.0),
)
.await;
match validation_result {
Ok(_) => {
println!("⚠️ Very small position was accepted (price data unavailable)");
}
Err(e) => {
let error_msg = e.to_string();
if error_msg.contains("minimum") {
println!("✅ Correctly rejected position below minimum size");
assert!(
error_msg.contains("minimum"),
"Error should mention minimum size"
);
} else {
println!("⚠️ Rejected for other reason: {}", error_msg);
}
}
}
}
Err(e) => {
println!("⚠️ Could not get minimum size: {}", e);
}
}
}
#[tokio::test]
async fn test_trading_hours_validation() {
let client = create_test_client().await.unwrap();
sleep(Duration::from_millis(500)).await;
let test_symbols = vec!["BTC/USD", "EUR/USD", "GOLD/USD"];
for symbol in test_symbols {
sleep(Duration::from_millis(200)).await;
match client.get_trading_hours(symbol).await {
Ok(hours) => {
println!(
"✅ Trading hours for {}: {}",
symbol,
if hours.is_open { "OPEN" } else { "CLOSED" }
);
let validation_result = client
.validate_trading_constraints(symbol, PositionSide::Long, dec!(1.0), dec!(5.0))
.await;
match validation_result {
Ok(_) => {
if hours.is_open {
println!("✅ Validation passed for open market");
} else {
println!("⚠️ Validation passed despite market being closed");
}
}
Err(e) => {
let error_msg = e.to_string();
if error_msg.contains("closed") && !hours.is_open {
println!("✅ Correctly blocked trade for closed market");
} else {
println!("⚠️ Validation failed for other reason: {}", error_msg);
}
}
}
}
Err(e) => {
println!("⚠️ Could not get trading hours for {}: {}", symbol, e);
}
}
}
}
#[tokio::test]
async fn test_open_interest_cap_validation() {
let client = create_test_client().await.unwrap();
sleep(Duration::from_millis(500)).await;
let test_cases = vec![
(
"BTC/USD",
PositionSide::Long,
dec!(0.1),
dec!(5.0),
"Normal long position",
),
(
"BTC/USD",
PositionSide::Short,
dec!(1000.0),
dec!(10.0),
"Very large short position",
),
(
"ETH/USD",
PositionSide::Long,
dec!(100.0),
dec!(20.0),
"Large ETH long position",
),
];
for (symbol, side, size, leverage, description) in test_cases {
sleep(Duration::from_millis(200)).await;
println!(
"Testing {}: {} {} at {}x leverage",
description, size, symbol, leverage
);
match client
.validate_trading_constraints(symbol, side, size, leverage)
.await
{
Ok(_) => {
println!("✅ {} passed validation", description);
}
Err(e) => {
let error_msg = e.to_string();
if error_msg.contains("interest") || error_msg.contains("exposure") {
println!(
"✅ {} correctly rejected due to exposure limits",
description
);
} else {
println!(
"⚠️ {} rejected for other reason: {}",
description, error_msg
);
}
}
}
}
}
#[tokio::test]
async fn test_complete_validation_workflow() {
let client = create_test_client().await.unwrap();
sleep(Duration::from_millis(500)).await;
let test_params = OpenPositionParams {
symbol: "BTC/USD".to_string(),
side: PositionSide::Long,
size: dec!(0.01), leverage: dec!(5.0),
take_profit: None,
stop_loss: None,
slippage_tolerance: dec!(0.02),
};
match client
.validate_trading_constraints(
&test_params.symbol,
test_params.side,
test_params.size,
test_params.leverage,
)
.await
{
Ok(_) => {
println!("✅ Complete validation workflow passed");
assert!(true, "Validation workflow is functional");
}
Err(e) => {
let error_msg = e.to_string();
println!("⚠️ Validation failed: {}", error_msg);
let is_constraint_error = error_msg.contains("minimum")
|| error_msg.contains("closed")
|| error_msg.contains("interest")
|| error_msg.contains("not found");
assert!(
is_constraint_error,
"Error should be from constraint validation, not system failure"
);
}
}
}
#[tokio::test]
async fn test_error_message_quality() {
let client = create_test_client().await.unwrap();
sleep(Duration::from_millis(500)).await;
match client
.validate_trading_constraints("BTC/USD", PositionSide::Long, dec!(0.000001), dec!(10.0))
.await
{
Ok(_) => {
println!("⚠️ Very small position was accepted (constraint may be disabled due to missing data)");
}
Err(e) => {
let error_msg = e.to_string();
println!("Error message: {}", error_msg);
let has_minimum_info = error_msg.contains("minimum");
let has_usdc_info = error_msg.contains("USDC");
let has_solutions = error_msg.contains("Solutions") || error_msg.contains("solution");
if has_minimum_info && (has_usdc_info || has_solutions) {
println!("✅ Error message is helpful and informative");
assert!(true, "Error message quality is good");
} else {
println!("⚠️ Error message could be more helpful");
assert!(
has_minimum_info || error_msg.contains("not found"),
"Error should at least mention minimum size or indicate missing data"
);
}
}
}
}
#[tokio::test]
async fn test_different_asset_types() {
let client = create_test_client().await.unwrap();
let asset_tests = vec![
("BTC/USD", "Cryptocurrency"),
("EUR/USD", "Forex"),
("GOLD/USD", "Commodity"),
("SPX/USD", "Index"),
];
for (symbol, category) in asset_tests {
sleep(Duration::from_millis(300)).await;
println!("Testing {} constraint validation ({})", symbol, category);
match client.get_minimum_position_size(symbol).await {
Ok(min_size) => {
println!("✅ {} minimum size: {}", symbol, min_size);
assert!(
min_size >= Decimal::ZERO,
"Minimum size should be non-negative"
);
match category {
"Cryptocurrency" => {
assert!(
min_size < dec!(1.0),
"Crypto minimum should be less than 1 unit"
);
}
"Forex" => {
}
"Commodity" | "Index" => {
}
_ => {}
}
}
Err(e) => {
println!("⚠️ Could not get minimum size for {}: {}", symbol, e);
}
}
}
}
#[tokio::test]
async fn test_constraint_integration() {
let client = create_test_client().await.unwrap();
sleep(Duration::from_millis(500)).await;
let validation_result = client
.validate_trading_constraints("BTC/USD", PositionSide::Long, dec!(0.01), dec!(5.0))
.await;
match validation_result {
Ok(_) => {
println!("✅ Constraint validation method is working and accessible");
assert!(true, "Validation method is properly integrated");
}
Err(e) => {
println!("⚠️ Constraint validation returned error: {}", e);
let error_msg = e.to_string();
let is_expected_error = error_msg.contains("not found")
|| error_msg.contains("minimum")
|| error_msg.contains("closed")
|| error_msg.contains("interest");
assert!(
is_expected_error,
"Error should be from constraint logic, not missing method"
);
println!(
"✅ Constraint validation method exists and runs (error is from constraint logic)"
);
}
}
}
#[tokio::test]
async fn test_constraint_validation_performance() {
let client = create_test_client().await.unwrap();
sleep(Duration::from_millis(500)).await;
let start_time = std::time::Instant::now();
for i in 0..3 {
sleep(Duration::from_millis(200)).await;
let _ = client
.validate_trading_constraints("BTC/USD", PositionSide::Long, dec!(0.01), dec!(5.0))
.await;
if i == 0 {
let elapsed = start_time.elapsed();
println!("First validation took: {:?}", elapsed);
assert!(
elapsed < Duration::from_secs(10),
"Constraint validation should complete within 10 seconds"
);
}
}
let total_elapsed = start_time.elapsed();
println!("Total time for 3 validations: {:?}", total_elapsed);
assert!(
total_elapsed < Duration::from_secs(30),
"Multiple validations should complete within reasonable time"
);
}