mirror of
https://github.com/floor-licker/polyfill-rs.git
synced 2026-08-14 21:18:07 +00:00
Initial commit (clean, no build artifacts)
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
//! Common utilities for integration tests
|
||||
|
||||
use polyfill_rs::{ClobClient, Result};
|
||||
use std::env;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Test configuration loaded from environment variables
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TestConfig {
|
||||
pub host: String,
|
||||
pub chain_id: u64,
|
||||
pub private_key: Option<String>,
|
||||
pub api_key: Option<String>,
|
||||
pub api_secret: Option<String>,
|
||||
pub api_passphrase: Option<String>,
|
||||
pub test_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Default for TestConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
host: env::var("POLYMARKET_HOST").unwrap_or_else(|_| "https://clob.polymarket.com".to_string()),
|
||||
chain_id: env::var("POLYMARKET_CHAIN_ID")
|
||||
.unwrap_or_else(|_| "137".to_string())
|
||||
.parse()
|
||||
.unwrap_or(137),
|
||||
private_key: env::var("POLYMARKET_PRIVATE_KEY").ok(),
|
||||
api_key: env::var("POLYMARKET_API_KEY").ok(),
|
||||
api_secret: env::var("POLYMARKET_API_SECRET").ok(),
|
||||
api_passphrase: env::var("POLYMARKET_API_PASSPHRASE").ok(),
|
||||
test_timeout: Duration::from_secs(30),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestConfig {
|
||||
/// Check if we have authentication credentials
|
||||
pub fn has_auth(&self) -> bool {
|
||||
self.private_key.is_some()
|
||||
}
|
||||
|
||||
/// Check if we have API credentials
|
||||
pub fn has_api_creds(&self) -> bool {
|
||||
self.api_key.is_some() && self.api_secret.is_some() && self.api_passphrase.is_some()
|
||||
}
|
||||
|
||||
/// Create a basic client for testing
|
||||
pub fn create_basic_client(&self) -> ClobClient {
|
||||
ClobClient::new(&self.host)
|
||||
}
|
||||
|
||||
/// Create an authenticated client for testing
|
||||
pub fn create_auth_client(&self) -> Result<ClobClient> {
|
||||
let private_key = self.private_key.as_ref()
|
||||
.ok_or_else(|| polyfill_rs::PolyfillError::auth("No private key provided", polyfill_rs::errors::AuthErrorKind::InvalidCredentials))?;
|
||||
|
||||
Ok(ClobClient::with_l1_headers(&self.host, private_key, self.chain_id))
|
||||
}
|
||||
|
||||
/// Print test configuration (without sensitive data)
|
||||
pub fn print_config(&self) {
|
||||
println!("Test Configuration:");
|
||||
println!(" Host: {}", self.host);
|
||||
println!(" Chain ID: {}", self.chain_id);
|
||||
println!(" Has Auth: {}", self.has_auth());
|
||||
println!(" Has API Creds: {}", self.has_api_creds());
|
||||
println!(" Timeout: {:?}", self.test_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
/// Test utilities for common operations
|
||||
pub struct TestUtils;
|
||||
|
||||
impl TestUtils {
|
||||
/// Get a valid token_id for testing
|
||||
pub async fn get_test_token_id(client: &ClobClient) -> Result<String> {
|
||||
let markets = client.get_sampling_markets(None).await?;
|
||||
if markets.data.is_empty() {
|
||||
return Err(polyfill_rs::PolyfillError::internal_simple("No markets available for testing"));
|
||||
}
|
||||
|
||||
let token_id = markets.data[0].tokens[0].token_id.clone();
|
||||
println!("Using test token_id: {}", token_id);
|
||||
Ok(token_id)
|
||||
}
|
||||
|
||||
/// Wait for a condition with timeout
|
||||
pub async fn wait_for<F, Fut>(mut condition: F, timeout: Duration) -> Result<()>
|
||||
where
|
||||
F: FnMut() -> Fut,
|
||||
Fut: std::future::Future<Output = Result<bool>>,
|
||||
{
|
||||
let start = std::time::Instant::now();
|
||||
let check_interval = Duration::from_millis(100);
|
||||
|
||||
while start.elapsed() < timeout {
|
||||
if condition().await? {
|
||||
return Ok(());
|
||||
}
|
||||
tokio::time::sleep(check_interval).await;
|
||||
}
|
||||
|
||||
Err(polyfill_rs::PolyfillError::timeout(
|
||||
timeout,
|
||||
"Condition not met within timeout".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Measure execution time of an async operation
|
||||
pub async fn measure_time<F, Fut, T>(operation: F) -> (T, Duration)
|
||||
where
|
||||
F: FnOnce() -> Fut,
|
||||
Fut: std::future::Future<Output = T>,
|
||||
{
|
||||
let start = std::time::Instant::now();
|
||||
let result = operation().await;
|
||||
let duration = start.elapsed();
|
||||
(result, duration)
|
||||
}
|
||||
|
||||
/// Assert that an operation completes within a reasonable time
|
||||
pub async fn assert_timely<F, Fut, T>(operation: F, max_duration: Duration) -> Result<T>
|
||||
where
|
||||
F: FnOnce() -> Fut,
|
||||
Fut: std::future::Future<Output = Result<T>>,
|
||||
{
|
||||
let (result, duration) = Self::measure_time(|| async {
|
||||
operation().await
|
||||
}).await;
|
||||
|
||||
if duration > max_duration {
|
||||
return Err(polyfill_rs::PolyfillError::timeout(
|
||||
duration,
|
||||
format!("Operation took too long: {:?} > {:?}", duration, max_duration),
|
||||
));
|
||||
}
|
||||
|
||||
println!("Operation completed in {:?}", duration);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Test result reporting
|
||||
pub struct TestReporter;
|
||||
|
||||
impl TestReporter {
|
||||
/// Report test success
|
||||
pub fn success(test_name: &str) {
|
||||
println!("✅ {} passed", test_name);
|
||||
}
|
||||
|
||||
/// Report test failure
|
||||
pub fn failure(test_name: &str, error: &dyn std::error::Error) {
|
||||
println!("❌ {} failed: {}", test_name, error);
|
||||
}
|
||||
|
||||
/// Report test skip
|
||||
pub fn skip(test_name: &str, reason: &str) {
|
||||
println!("⚠️ {} skipped: {}", test_name, reason);
|
||||
}
|
||||
|
||||
/// Report test performance
|
||||
pub fn performance(test_name: &str, duration: Duration) {
|
||||
println!("⚡ {} completed in {:?}", test_name, duration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
//! Integration tests for polyfill-rs
|
||||
//!
|
||||
//! 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 rust_decimal::Decimal;
|
||||
use std::str::FromStr;
|
||||
use std::env;
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
||||
const POLYMARKET_HOST: &str = "https://clob.polymarket.com";
|
||||
const POLYGON_CHAIN_ID: u64 = 137;
|
||||
|
||||
/// Test configuration from environment variables
|
||||
struct TestConfig {
|
||||
private_key: Option<String>,
|
||||
api_key: Option<String>,
|
||||
api_secret: Option<String>,
|
||||
api_passphrase: Option<String>,
|
||||
}
|
||||
|
||||
impl TestConfig {
|
||||
fn from_env() -> Self {
|
||||
Self {
|
||||
private_key: env::var("POLYMARKET_PRIVATE_KEY").ok(),
|
||||
api_key: env::var("POLYMARKET_API_KEY").ok(),
|
||||
api_secret: env::var("POLYMARKET_API_SECRET").ok(),
|
||||
api_passphrase: env::var("POLYMARKET_API_PASSPHRASE").ok(),
|
||||
}
|
||||
}
|
||||
|
||||
fn has_auth(&self) -> bool {
|
||||
self.private_key.is_some()
|
||||
}
|
||||
|
||||
fn has_api_creds(&self) -> bool {
|
||||
self.api_key.is_some() && self.api_secret.is_some() && self.api_passphrase.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that we can connect to Polymarket's API
|
||||
#[tokio::test]
|
||||
async fn test_api_connectivity() -> Result<()> {
|
||||
let client = ClobClient::new(POLYMARKET_HOST);
|
||||
|
||||
// Test basic connectivity
|
||||
let is_ok = client.get_ok().await;
|
||||
assert!(is_ok, "Failed to connect to Polymarket API");
|
||||
|
||||
// Test server time endpoint
|
||||
let server_time = client.get_server_time().await?;
|
||||
assert!(server_time > 0, "Invalid server time received");
|
||||
|
||||
println!("✅ API connectivity test passed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test market data endpoints
|
||||
#[tokio::test]
|
||||
async fn test_market_data_endpoints() -> Result<()> {
|
||||
let client = ClobClient::new(POLYMARKET_HOST);
|
||||
|
||||
// Get sampling markets to find a valid token_id
|
||||
let markets_response = client.get_sampling_markets(None).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 with token_id: {}", token_id);
|
||||
|
||||
// Test order book endpoint
|
||||
let order_book = client.get_order_book(token_id).await?;
|
||||
assert_eq!(order_book.asset_id, *token_id);
|
||||
assert!(!order_book.bids.is_empty() || !order_book.asks.is_empty(), "Empty order book");
|
||||
|
||||
// Test midpoint endpoint
|
||||
let midpoint = client.get_midpoint(token_id).await?;
|
||||
assert!(midpoint.mid > Decimal::ZERO, "Invalid midpoint");
|
||||
|
||||
// Test spread endpoint
|
||||
let spread = client.get_spread(token_id).await?;
|
||||
assert!(spread.spread >= Decimal::ZERO, "Invalid spread");
|
||||
|
||||
// Test price endpoints
|
||||
let buy_price = client.get_price(token_id, polyfill_rs::Side::BUY).await?;
|
||||
let sell_price = client.get_price(token_id, polyfill_rs::Side::SELL).await?;
|
||||
assert!(buy_price.price > Decimal::ZERO, "Invalid buy price");
|
||||
assert!(sell_price.price > Decimal::ZERO, "Invalid sell price");
|
||||
|
||||
// Test tick size endpoint
|
||||
let tick_size = client.get_tick_size(token_id).await?;
|
||||
assert!(tick_size > Decimal::ZERO, "Invalid tick size");
|
||||
|
||||
// Test neg risk endpoint
|
||||
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");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test error handling with invalid requests
|
||||
#[tokio::test]
|
||||
async fn test_error_handling() -> Result<()> {
|
||||
let client = ClobClient::new(POLYMARKET_HOST);
|
||||
|
||||
// Test with invalid token_id
|
||||
let result = client.get_order_book("invalid_token_id").await;
|
||||
match result {
|
||||
Ok(_) => {
|
||||
// Some APIs might return empty data instead of error
|
||||
println!("⚠️ Invalid token_id returned data instead of error");
|
||||
}
|
||||
Err(e) => {
|
||||
match e {
|
||||
PolyfillError::Api { status, .. } => {
|
||||
assert!(status >= 400, "Expected client/server error for invalid token");
|
||||
}
|
||||
_ => {
|
||||
// Other error types are also acceptable
|
||||
println!("Received error for invalid token: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("✅ Error handling test passed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test rate limiting behavior
|
||||
#[tokio::test]
|
||||
async fn test_rate_limiting() -> Result<()> {
|
||||
let client = ClobClient::new(POLYMARKET_HOST);
|
||||
|
||||
// Make multiple rapid requests to test rate limiting
|
||||
let mut results = Vec::new();
|
||||
for _ in 0..5 {
|
||||
let result = client.get_server_time().await;
|
||||
results.push(result);
|
||||
|
||||
// Small delay between requests
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
// Most requests should succeed
|
||||
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");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test compatibility with polymarket-rs-client API
|
||||
#[tokio::test]
|
||||
async fn test_api_compatibility() -> Result<()> {
|
||||
// Test that our client has the same basic structure as polymarket-rs-client
|
||||
let client = ClobClient::new(POLYMARKET_HOST);
|
||||
|
||||
// Test that we can call the same methods
|
||||
let _ = client.get_ok().await;
|
||||
let _ = client.get_server_time().await?;
|
||||
let _ = client.get_sampling_markets(None).await?;
|
||||
|
||||
// Test that our types are compatible
|
||||
let order_args = polyfill_rs::OrderArgs::new(
|
||||
"test_token",
|
||||
Decimal::from_str("0.5").map_err(|e| PolyfillError::parse(format!("Invalid decimal: {}", e), None))?,
|
||||
Decimal::from_str("1.0").map_err(|e| PolyfillError::parse(format!("Invalid decimal: {}", e), None))?,
|
||||
polyfill_rs::Side::BUY,
|
||||
);
|
||||
|
||||
assert_eq!(order_args.token_id, "test_token");
|
||||
assert_eq!(order_args.side, polyfill_rs::Side::BUY);
|
||||
|
||||
println!("✅ API compatibility test passed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test performance characteristics
|
||||
#[tokio::test]
|
||||
async fn test_performance() -> Result<()> {
|
||||
let client = ClobClient::new(POLYMARKET_HOST);
|
||||
|
||||
// Test response time for basic operations
|
||||
let start = std::time::Instant::now();
|
||||
let _ = client.get_server_time().await?;
|
||||
let server_time_duration = start.elapsed();
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
let markets = client.get_sampling_markets(None).await?;
|
||||
let markets_duration = start.elapsed();
|
||||
|
||||
// Verify reasonable performance (adjust thresholds as needed)
|
||||
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!(" Server time: {:?}", server_time_duration);
|
||||
println!(" Markets request: {:?}", markets_duration);
|
||||
println!(" Markets returned: {}", markets.data.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user