diff --git a/examples/quick_demo.rs b/examples/quick_demo.rs new file mode 100644 index 0000000..de9c969 --- /dev/null +++ b/examples/quick_demo.rs @@ -0,0 +1,314 @@ +//! Quick Demo for polyfill-rs +//! +//! This example demonstrates all available API endpoints in a simple, easy-to-run format. +//! It can be run without authentication credentials and will test all public endpoints. + +use polyfill_rs::{ClobClient, Side, Result, PolyfillError}; +use rust_decimal::Decimal; +use std::str::FromStr; +use tokio::time::{sleep, Duration}; +use tracing::{info, error, warn}; + +/// Quick demo that tests all available endpoints +#[tokio::main] +async fn main() -> Result<()> { + // Initialize logging + tracing_subscriber::fmt::init(); + + info!("Polyfill-rs Quick Demo"); + info!("======================"); + + // Create client + let client = ClobClient::new("https://clob.polymarket.com"); + + // Test 1: Basic connectivity + info!("\nTesting API Connectivity..."); + match test_connectivity(&client).await { + Ok(_) => info!("API connectivity test passed"), + Err(e) => { + error!("API connectivity test failed: {}", e); + return Err(e); + } + } + + // Test 2: Get a valid token ID from markets + info!("\nGetting Market Data..."); + let token_id = match get_valid_token_id(&client).await { + Ok(id) => { + info!("Found valid token ID: {}", id); + id + } + Err(e) => { + error!("Failed to get valid token ID: {}", e); + return Err(e); + } + }; + + // Test 3: Test all market data endpoints + info!("\nTesting Market Data Endpoints..."); + test_market_data_endpoints(&client, &token_id).await?; + + // Test 4: Test error handling + info!("\nTesting Error Handling..."); + test_error_handling(&client).await?; + + // Test 5: Performance test + info!("\nTesting Performance..."); + test_performance(&client, &token_id).await?; + + info!("\nAll tests completed successfully!"); + info!("The polyfill-rs client is working correctly with the Polymarket API."); + + Ok(()) +} + +/// Test basic API connectivity +async fn test_connectivity(client: &ClobClient) -> Result<()> { + // Test /ok endpoint + let is_ok = client.get_ok().await; + if !is_ok { + return Err(PolyfillError::network("API not responding", std::io::Error::new(std::io::ErrorKind::Other, "API not responding"))); + } + info!(" /ok endpoint responding"); + + // Test /time endpoint + let server_time = client.get_server_time().await?; + info!(" Server time: {}", server_time); + + // Verify server time is reasonable (within last 24 hours) + let current_time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + + let time_diff = if server_time > current_time { + server_time - current_time + } else { + current_time - server_time + }; + + if time_diff > 86400 { // 24 hours + warn!(" Server time seems off (diff: {} seconds)", time_diff); + } else { + info!(" Server time is reasonable"); + } + + Ok(()) +} + +/// Get a valid token ID from the markets endpoint +async fn get_valid_token_id(client: &ClobClient) -> Result { + let markets = client.get_sampling_markets(Some(10)).await?; + + if markets.data.is_empty() { + return Err(PolyfillError::api(404, "No markets found")); + } + + // Find a market with active tokens + for market in &markets.data { + if market.active && !market.closed { + for token in &market.tokens { + if !token.token_id.is_empty() { + info!(" Found active market: {}", market.question); + info!(" Market slug: {}", market.market_slug); + info!(" Token ID: {}", token.token_id); + info!(" Outcome: {}", token.outcome); + return Ok(token.token_id.clone()); + } + } + } + } + + Err(PolyfillError::api(404, "No active markets with valid tokens found")) +} + +/// Test all market data endpoints +async fn test_market_data_endpoints(client: &ClobClient, token_id: &str) -> Result<()> { + // Test order book + info!(" Testing order book endpoint..."); + let order_book = client.get_order_book(token_id).await?; + info!(" Order book: {} bids, {} asks", order_book.bids.len(), order_book.asks.len()); + + // Test midpoint + info!(" Testing midpoint endpoint..."); + let midpoint = client.get_midpoint(token_id).await?; + info!(" Midpoint: {}", midpoint.mid); + + // Test spread + info!(" Testing spread endpoint..."); + let spread = client.get_spread(token_id).await?; + info!(" Spread: {}", spread.spread); + + // Test buy price + info!(" Testing buy price endpoint..."); + let buy_price = client.get_price(token_id, Side::BUY).await?; + info!(" Buy price: {}", buy_price.price); + + // Test sell price + info!(" Testing sell price endpoint..."); + let sell_price = client.get_price(token_id, Side::SELL).await?; + info!(" Sell price: {}", sell_price.price); + + // Test tick size + info!(" Testing tick size endpoint..."); + let tick_size = client.get_tick_size(token_id).await?; + info!(" Tick size: {}", tick_size); + + // Test neg risk + info!(" Testing neg risk endpoint..."); + let neg_risk = client.get_neg_risk(token_id).await?; + info!(" Neg risk: {}", neg_risk); + + // Validate data consistency + info!(" Validating data consistency..."); + validate_market_data(&order_book, &midpoint, &spread, &buy_price, &sell_price)?; + + Ok(()) +} + +/// Validate that market data is consistent +fn validate_market_data( + order_book: &polyfill_rs::client::OrderBookSummary, + midpoint: &polyfill_rs::client::MidpointResponse, + spread: &polyfill_rs::client::SpreadResponse, + buy_price: &polyfill_rs::client::PriceResponse, + sell_price: &polyfill_rs::client::PriceResponse, +) -> Result<()> { + // Check that we have some liquidity + if order_book.bids.is_empty() && order_book.asks.is_empty() { + warn!(" Order book is empty"); + } else { + info!(" Order book has liquidity"); + } + + // Check that prices are positive + if buy_price.price <= Decimal::ZERO { + warn!(" Buy price is not positive: {}", buy_price.price); + } else { + info!(" Buy price is positive"); + } + + if sell_price.price <= Decimal::ZERO { + warn!(" Sell price is not positive: {}", sell_price.price); + } else { + info!(" Sell price is positive"); + } + + // Check that spread is reasonable + if spread.spread < Decimal::ZERO { + warn!(" Spread is negative: {}", spread.spread); + } else { + info!(" Spread is non-negative"); + } + + // Check that midpoint is between buy and sell prices (if both exist) + if buy_price.price > Decimal::ZERO && sell_price.price > Decimal::ZERO { + if midpoint.mid < buy_price.price || midpoint.mid > sell_price.price { + warn!(" Midpoint {} is not between buy {} and sell {}", + midpoint.mid, buy_price.price, sell_price.price); + } else { + info!(" Midpoint is between buy and sell prices"); + } + } + + Ok(()) +} + +/// Test error handling with invalid requests +async fn test_error_handling(client: &ClobClient) -> Result<()> { + // Test with invalid token ID + info!(" Testing invalid token ID..."); + let result = client.get_order_book("invalid_token_12345").await; + match result { + Ok(_) => { + warn!(" Invalid token ID returned data instead of error"); + } + Err(e) => { + match e { + PolyfillError::Api { status, .. } => { + if status >= 400 { + info!(" Invalid token ID correctly returned error: {}", status); + } else { + warn!(" Unexpected status code for invalid token: {}", status); + } + } + _ => { + info!(" Invalid token ID returned error: {:?}", e); + } + } + } + } + + // Test with empty token ID + info!(" Testing empty token ID..."); + let result = client.get_order_book("").await; + match result { + Ok(_) => { + warn!(" Empty token ID returned data instead of error"); + } + Err(e) => { + info!(" Empty token ID correctly returned error: {:?}", e); + } + } + + Ok(()) +} + +/// Test performance characteristics +async fn test_performance(client: &ClobClient, token_id: &str) -> Result<()> { + let mut total_time = Duration::from_secs(0); + let mut success_count = 0; + let test_count = 5; + + info!(" Running {} performance tests...", test_count); + + for i in 1..=test_count { + let start = std::time::Instant::now(); + + // Test a mix of endpoints + let results = tokio::join!( + client.get_server_time(), + client.get_midpoint(token_id), + client.get_spread(token_id), + ); + + let duration = start.elapsed(); + total_time += duration; + + match results { + (Ok(_), Ok(_), Ok(_)) => { + success_count += 1; + info!(" Test {}: PASSED {:.2}ms", i, duration.as_secs_f64() * 1000.0); + } + _ => { + warn!(" Test {}: FAILED in {:.2}ms", i, duration.as_secs_f64() * 1000.0); + } + } + + // Small delay between tests + sleep(Duration::from_millis(100)).await; + } + + let avg_time = total_time / test_count as u32; + let success_rate = (success_count as f64 / test_count as f64) * 100.0; + + info!(" Performance Summary:"); + info!(" Success rate: {:.1}%", success_rate); + info!(" Average response time: {:.2}ms", avg_time.as_secs_f64() * 1000.0); + info!(" Total time: {:.2}s", total_time.as_secs_f64()); + + // Performance thresholds + if avg_time > Duration::from_secs(2) { + warn!(" Average response time is slow: {:.2}ms", avg_time.as_secs_f64() * 1000.0); + } else { + info!(" Response times are acceptable"); + } + + if success_rate < 80.0 { + warn!(" Success rate is low: {:.1}%", success_rate); + } else { + info!(" Success rate is good"); + } + + Ok(()) +} diff --git a/scripts/run_demo.sh b/scripts/run_demo.sh new file mode 100755 index 0000000..d0d0fcc --- /dev/null +++ b/scripts/run_demo.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Demo runner for polyfill-rs +# This script runs the quick demo to showcase all available endpoints + +set -e + +echo "Running polyfill-rs Quick Demo" +echo "=================================" + +# Check if we're in the right directory +if [ ! -f "Cargo.toml" ]; then + echo " Error: Please run this script from the project root directory" + exit 1 +fi + +# Check if the demo example exists +if [ ! -f "examples/quick_demo.rs" ]; then + echo " Error: Quick demo example not found" + exit 1 +fi + +echo "Configuration:" +echo " Host: https://clob.polymarket.com" +echo " Chain ID: 137 (Polygon)" +echo " Authentication: None required (public endpoints only)" +echo "" + +# Run the quick demo +echo "Running quick demo..." +echo "====================" + +cargo run --example quick_demo + +if [ $? -eq 0 ]; then + echo "" + echo " Quick demo completed successfully!" + echo "" + echo "What was tested:" + echo " API connectivity (/ok, /time)" + echo " Market data retrieval (/sampling-markets)" + echo " Order book data (/book)" + echo " Price data (/midpoint, /spread, /price)" + echo " Market metadata (/tick-size, /neg-risk)" + echo " Error handling (invalid inputs)" + echo " Performance characteristics" + echo " Data consistency validation" + echo "" + echo "The polyfill-rs client is working correctly with the Polymarket API!" +else + echo "" + echo " Quick demo failed!" + echo "Check the output above for error details." + exit 1 +fi diff --git a/src/book.rs b/src/book.rs index 4eeff50..1131517 100644 --- a/src/book.rs +++ b/src/book.rs @@ -567,21 +567,26 @@ impl OrderBook { /// Get the total liquidity at a given price level /// Tells you how much you can buy/sell at exactly this price pub fn liquidity_at_price(&self, price: Decimal, side: Side) -> Decimal { + let price_u32 = decimal_to_price(price).unwrap_or(0); match side { - Side::BUY => self.asks.get(&price).copied().unwrap_or_default(), // How much we can buy at this price - Side::SELL => self.bids.get(&price).copied().unwrap_or_default(), // How much we can sell at this price + Side::BUY => Decimal::from(self.asks.get(&price_u32).copied().unwrap_or_default()), // How much we can buy at this price + Side::SELL => Decimal::from(self.bids.get(&price_u32).copied().unwrap_or_default()), // How much we can sell at this price } } /// Get the total liquidity within a price range /// Useful for understanding how much depth exists in a certain price band pub fn liquidity_in_range(&self, min_price: Decimal, max_price: Decimal, side: Side) -> Decimal { + let min_price_u32 = decimal_to_price(min_price).unwrap_or(0); + let max_price_u32 = decimal_to_price(max_price).unwrap_or(0); + let levels: Vec<_> = match side { - Side::BUY => self.asks.range(min_price..=max_price).collect(), - Side::SELL => self.bids.range(min_price..=max_price).rev().collect(), + Side::BUY => self.asks.range(min_price_u32..=max_price_u32).collect(), + Side::SELL => self.bids.range(min_price_u32..=max_price_u32).rev().collect(), }; - levels.into_iter().map(|(_, &size)| size).sum() + let total: i64 = levels.into_iter().map(|(_, &size)| size).sum(); + Decimal::from(total) } /// Validate that prices are properly ordered @@ -738,8 +743,8 @@ impl OrderBook { pub fn analytics(&self) -> BookAnalytics { let bid_count = self.bids.len(); let ask_count = self.asks.len(); - let total_bid_size: Decimal = self.bids.values().sum(); // Add up all bid sizes - let total_ask_size: Decimal = self.asks.values().sum(); // Add up all ask sizes + let total_bid_size: Decimal = Decimal::from(self.bids.values().sum::()); // Add up all bid sizes + let total_ask_size: Decimal = Decimal::from(self.asks.values().sum::()); // Add up all ask sizes BookAnalytics { token_id: self.token_id.clone(), diff --git a/src/types.rs b/src/types.rs index 88960a1..b7def27 100644 --- a/src/types.rs +++ b/src/types.rs @@ -6,6 +6,7 @@ use alloy_primitives::{Address, U256}; use chrono::{DateTime, Utc}; use rust_decimal::Decimal; +use rust_decimal::prelude::ToPrimitive; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use uuid::Uuid; @@ -90,7 +91,7 @@ pub const MAX_QTY: Qty = Qty::MAX / 2; // Leave room for intermediate calculatio /// - decimal_to_price(Decimal::from_str("0.6543")) = Ok(6543) /// - decimal_to_price(Decimal::from_str("1.0000")) = Ok(10000) /// - decimal_to_price(Decimal::from_str("0.00005")) = Ok(1) // Rounds up to min tick -pub fn decimal_to_price(decimal: Decimal) -> Result { +pub fn decimal_to_price(decimal: Decimal) -> std::result::Result { // Convert to fixed-point by multiplying by scale factor let scaled = decimal * Decimal::from(SCALE_FACTOR); @@ -131,7 +132,7 @@ pub fn price_to_decimal(ticks: Price) -> Decimal { /// Examples: /// - decimal_to_qty(Decimal::from_str("100.0")) = Ok(1000000) /// - decimal_to_qty(Decimal::from_str("-50.5")) = Ok(-505000) -pub fn decimal_to_qty(decimal: Decimal) -> Result { +pub fn decimal_to_qty(decimal: Decimal) -> std::result::Result { let scaled = decimal * Decimal::from(SCALE_FACTOR); let rounded = scaled.round(); @@ -299,7 +300,7 @@ impl FastBookLevel { /// Create from external BookLevel (with validation) /// This is called when we receive data from the API - pub fn from_book_level(level: &BookLevel) -> Result { + pub fn from_book_level(level: &BookLevel) -> std::result::Result { let price = decimal_to_price(level.price)?; let size = decimal_to_qty(level.size)?; Ok(Self::new(price, size)) @@ -374,7 +375,7 @@ impl FastOrderDelta { /// This is where we enforce tick alignment - if the incoming price /// doesn't align to valid ticks, we either reject it or round it. /// This prevents bad data from corrupting our order book. - pub fn from_order_delta(delta: &OrderDelta, tick_size: Option) -> Result { + pub fn from_order_delta(delta: &OrderDelta, tick_size: Option) -> std::result::Result { // Validate tick alignment if we have a tick size if let Some(tick_size) = tick_size { if !is_price_tick_aligned(delta.price, tick_size) { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index c2848bf..72f0924 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -3,11 +3,12 @@ //! These tests verify that our client can actually communicate with the real Polymarket API. //! They require network connectivity and may take longer to run. -use polyfill_rs::{ClobClient, Result, PolyfillError}; +use polyfill_rs::{ClobClient, Result, PolyfillError, Side}; use rust_decimal::Decimal; use std::str::FromStr; use std::env; use tokio::time::{sleep, Duration}; +use tracing::{info, error, warn}; const POLYMARKET_HOST: &str = "https://clob.polymarket.com"; const POLYGON_CHAIN_ID: u64 = 137; @@ -52,7 +53,7 @@ async fn test_api_connectivity() -> Result<()> { let server_time = client.get_server_time().await?; assert!(server_time > 0, "Invalid server time received"); - println!("✅ API connectivity test passed"); + println!("API connectivity test passed"); Ok(()) } @@ -97,7 +98,7 @@ async fn test_market_data_endpoints() -> Result<()> { let neg_risk = client.get_neg_risk(token_id).await?; // neg_risk is a boolean, so just verify it doesn't panic - println!("✅ Market data endpoints test passed"); + println!("Market data endpoints test passed"); Ok(()) } @@ -111,7 +112,7 @@ async fn test_error_handling() -> Result<()> { match result { Ok(_) => { // Some APIs might return empty data instead of error - println!("⚠️ Invalid token_id returned data instead of error"); + println!("Invalid token_id returned data instead of error"); } Err(e) => { match e { @@ -126,7 +127,7 @@ async fn test_error_handling() -> Result<()> { } } - println!("✅ Error handling test passed"); + println!(" Error handling test passed"); Ok(()) } @@ -149,7 +150,7 @@ async fn test_rate_limiting() -> Result<()> { let success_count = results.iter().filter(|r| r.is_ok()).count(); assert!(success_count >= 3, "Too many requests failed: {}/5", success_count); - println!("✅ Rate limiting test passed"); + println!(" Rate limiting test passed"); Ok(()) } @@ -175,7 +176,7 @@ async fn test_api_compatibility() -> Result<()> { assert_eq!(order_args.token_id, "test_token"); assert_eq!(order_args.side, polyfill_rs::Side::BUY); - println!("✅ API compatibility test passed"); + println!(" API compatibility test passed"); Ok(()) } @@ -197,10 +198,268 @@ async fn test_performance() -> Result<()> { assert!(server_time_duration < Duration::from_secs(5), "Server time too slow: {:?}", server_time_duration); assert!(markets_duration < Duration::from_secs(10), "Markets request too slow: {:?}", markets_duration); - println!("✅ Performance test passed"); + println!(" Performance test passed"); println!(" Server time: {:?}", server_time_duration); println!(" Markets request: {:?}", markets_duration); println!(" Markets returned: {}", markets.data.len()); + Ok(()) +} + +/// Comprehensive test of all market data endpoints +#[tokio::test] +async fn test_all_market_data_endpoints() -> Result<()> { + let client = ClobClient::new(POLYMARKET_HOST); + + // Get a valid token ID first + let markets_response = client.get_sampling_markets(Some(5)).await?; + assert!(!markets_response.data.is_empty(), "No markets returned"); + + let first_market = &markets_response.data[0]; + let token_id = &first_market.tokens[0].token_id; + + println!("Testing all endpoints with token_id: {}", token_id); + + // Test all endpoints systematically + let endpoints = vec![ + ("Order Book", Box::new(|| client.get_order_book(token_id)) as Box _>), + ("Midpoint", Box::new(|| client.get_midpoint(token_id))), + ("Spread", Box::new(|| client.get_spread(token_id))), + ("Buy Price", Box::new(|| client.get_price(token_id, Side::BUY))), + ("Sell Price", Box::new(|| client.get_price(token_id, Side::SELL))), + ("Tick Size", Box::new(|| client.get_tick_size(token_id))), + ("Neg Risk", Box::new(|| client.get_neg_risk(token_id))), + ]; + + let mut success_count = 0; + let mut total_time = Duration::from_secs(0); + + for (name, endpoint_test) in endpoints { + let start = std::time::Instant::now(); + match endpoint_test().await { + Ok(_) => { + let duration = start.elapsed(); + total_time += duration; + success_count += 1; + println!(" {}: {:.2}ms", name, duration.as_secs_f64() * 1000.0); + } + Err(e) => { + let duration = start.elapsed(); + total_time += duration; + println!(" {}: {:.2}ms - {}", name, duration.as_secs_f64() * 1000.0, e); + } + } + + // Small delay between requests + sleep(Duration::from_millis(100)).await; + } + + let avg_time = total_time / endpoints.len() as u32; + let success_rate = (success_count as f64 / endpoints.len() as f64) * 100.0; + + println!("Endpoint Test Summary:"); + println!(" Success rate: {:.1}%", success_rate); + println!(" Average response time: {:.2}ms", avg_time.as_secs_f64() * 1000.0); + + // Require at least 80% success rate + assert!(success_rate >= 80.0, "Success rate too low: {:.1}%", success_rate); + + Ok(()) +} + +/// Test data consistency across endpoints +#[tokio::test] +async fn test_data_consistency() -> Result<()> { + let client = ClobClient::new(POLYMARKET_HOST); + + // Get a valid token ID + let markets_response = client.get_sampling_markets(Some(5)).await?; + let first_market = &markets_response.data[0]; + let token_id = &first_market.tokens[0].token_id; + + println!("Testing data consistency with token_id: {}", token_id); + + // Get all market data + let order_book = client.get_order_book(token_id).await?; + let midpoint = client.get_midpoint(token_id).await?; + let spread = client.get_spread(token_id).await?; + let buy_price = client.get_price(token_id, Side::BUY).await?; + let sell_price = client.get_price(token_id, Side::SELL).await?; + let tick_size = client.get_tick_size(token_id).await?; + let neg_risk = client.get_neg_risk(token_id).await?; + + // Validate data consistency + println!("Data validation:"); + + // Check that prices are positive + assert!(buy_price.price > Decimal::ZERO, "Buy price should be positive: {}", buy_price.price); + assert!(sell_price.price > Decimal::ZERO, "Sell price should be positive: {}", sell_price.price); + println!(" Prices are positive"); + + // Check that spread is non-negative + assert!(spread.spread >= Decimal::ZERO, "Spread should be non-negative: {}", spread.spread); + println!(" Spread is non-negative"); + + // Check that tick size is positive + assert!(tick_size > Decimal::ZERO, "Tick size should be positive: {}", tick_size); + println!(" Tick size is positive"); + + // Check that midpoint is reasonable (between buy and sell if both exist) + if buy_price.price > Decimal::ZERO && sell_price.price > Decimal::ZERO { + if midpoint.mid < buy_price.price || midpoint.mid > sell_price.price { + println!(" Midpoint {} is not between buy {} and sell {}", + midpoint.mid, buy_price.price, sell_price.price); + } else { + println!(" Midpoint is between buy and sell prices"); + } + } + + // Check that we have some order book data + if order_book.bids.is_empty() && order_book.asks.is_empty() { + println!(" Order book is empty"); + } else { + println!(" Order book has liquidity ({} bids, {} asks)", + order_book.bids.len(), order_book.asks.len()); + } + + // Check neg risk is a boolean + println!(" Neg risk is boolean: {}", neg_risk); + + println!(" Data consistency test passed"); + Ok(()) +} + +/// Test rate limiting behavior more thoroughly +#[tokio::test] +async fn test_comprehensive_rate_limiting() -> Result<()> { + let client = ClobClient::new(POLYMARKET_HOST); + + println!("Testing rate limiting with rapid requests..."); + + // Make many rapid requests + let mut results = Vec::new(); + let request_count = 20; + + for i in 0..request_count { + let start = std::time::Instant::now(); + let result = client.get_server_time().await; + let duration = start.elapsed(); + + results.push((i, result, duration)); + + // Very small delay to test rate limiting + sleep(Duration::from_millis(50)).await; + } + + // Analyze results + let success_count = results.iter().filter(|(_, result, _)| result.is_ok()).count(); + let failure_count = results.iter().filter(|(_, result, _)| result.is_err()).count(); + + let success_rate = (success_count as f64 / request_count as f64) * 100.0; + + println!("Rate limiting test results:"); + println!(" Total requests: {}", request_count); + println!(" Successful: {}", success_count); + println!(" Failed: {}", failure_count); + println!(" Success rate: {:.1}%", success_rate); + + // Show timing for first few requests + for (i, result, duration) in results.iter().take(5) { + let status = if result.is_ok() { "" } else { "" }; + println!(" Request {}: {} {:.2}ms", i + 1, status, duration.as_secs_f64() * 1000.0); + } + + // We expect some failures due to rate limiting, but not too many + assert!(success_rate >= 50.0, "Success rate too low, possible rate limiting issues: {:.1}%", success_rate); + + println!(" Rate limiting test passed"); + Ok(()) +} + +/// Test error handling with various invalid inputs +#[tokio::test] +async fn test_comprehensive_error_handling() -> Result<()> { + let client = ClobClient::new(POLYMARKET_HOST); + + println!("Testing comprehensive error handling..."); + + // Test various invalid token IDs + let invalid_tokens = vec![ + "", + "invalid", + "12345", + "0x1234567890123456789012345678901234567890", + "very_long_invalid_token_id_that_should_fail", + ]; + + for token_id in invalid_tokens { + println!(" Testing invalid token ID: '{}'", token_id); + + let result = client.get_order_book(token_id).await; + match result { + Ok(order_book) => { + if order_book.bids.is_empty() && order_book.asks.is_empty() { + println!(" Empty order book returned (acceptable)"); + } else { + println!(" Unexpected data returned for invalid token"); + } + } + Err(e) => { + match e { + PolyfillError::Api { status, .. } => { + if status >= 400 { + println!(" Correctly returned API error: {}", status); + } else { + println!(" Unexpected status code: {}", status); + } + } + _ => { + println!(" Correctly returned error: {:?}", e); + } + } + } + } + } + + println!(" Error handling test passed"); + Ok(()) +} + +/// Test concurrent requests +#[tokio::test] +async fn test_concurrent_requests() -> Result<()> { + let client = ClobClient::new(POLYMARKET_HOST); + + println!("Testing concurrent requests..."); + + // Get a valid token ID + let markets_response = client.get_sampling_markets(Some(5)).await?; + let token_id = &markets_response.data[0].tokens[0].token_id; + + // Make concurrent requests + let start = std::time::Instant::now(); + + let results = tokio::join!( + client.get_server_time(), + client.get_midpoint(token_id), + client.get_spread(token_id), + client.get_price(token_id, Side::BUY), + client.get_price(token_id, Side::SELL), + ); + + let duration = start.elapsed(); + + // Check results + let (server_time, midpoint, spread, buy_price, sell_price) = results; + + assert!(server_time.is_ok(), "Server time request failed"); + assert!(midpoint.is_ok(), "Midpoint request failed"); + assert!(spread.is_ok(), "Spread request failed"); + assert!(buy_price.is_ok(), "Buy price request failed"); + assert!(sell_price.is_ok(), "Sell price request failed"); + + println!(" All concurrent requests succeeded"); + println!(" Total time: {:.2}ms", duration.as_secs_f64() * 1000.0); + Ok(()) } \ No newline at end of file