From 0d13e93705341128580b726659302a1fdb7921eb Mon Sep 17 00:00:00 2001 From: floor-licker Date: Tue, 4 Nov 2025 23:26:07 -0500 Subject: [PATCH] tests: unit tests for client.rs and auth.rs --- src/auth.rs | 147 +++++++++ src/client.rs | 457 +++++++++++++++++++++++++- src/orders.rs | 214 ++++++++++++ tarpaulin-report.html | 737 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1554 insertions(+), 1 deletion(-) create mode 100644 tarpaulin-report.html diff --git a/src/auth.rs b/src/auth.rs index 3e5fdce..a3e64c2 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -188,6 +188,7 @@ where #[cfg(test)] mod tests { use super::*; + use crate::client::ApiCreds; #[test] fn test_unix_timestamp() { @@ -206,4 +207,150 @@ mod tests { ); assert!(result.is_ok()); } + + #[test] + fn test_hmac_signature_with_body() { + let body = r#"{"test": "data"}"#; + let result = build_hmac_signature( + "test_secret", + 1234567890, + "POST", + "/orders", + Some(body), + ); + assert!(result.is_ok()); + let signature = result.unwrap(); + assert!(!signature.is_empty()); + } + + #[test] + fn test_hmac_signature_consistency() { + let secret = "test_secret"; + let timestamp = 1234567890; + let method = "GET"; + let path = "/test"; + + let sig1 = build_hmac_signature::(secret, timestamp, method, path, None).unwrap(); + let sig2 = build_hmac_signature::(secret, timestamp, method, path, None).unwrap(); + + // Same inputs should produce same signature + assert_eq!(sig1, sig2); + } + + #[test] + fn test_hmac_signature_different_inputs() { + let secret = "test_secret"; + let timestamp = 1234567890; + + let sig1 = build_hmac_signature::(secret, timestamp, "GET", "/test", None).unwrap(); + let sig2 = build_hmac_signature::(secret, timestamp, "POST", "/test", None).unwrap(); + let sig3 = build_hmac_signature::(secret, timestamp, "GET", "/other", None).unwrap(); + + // Different inputs should produce different signatures + assert_ne!(sig1, sig2); + assert_ne!(sig1, sig3); + assert_ne!(sig2, sig3); + } + + #[test] + fn test_create_l1_headers() { + use alloy_signer_local::PrivateKeySigner; + use alloy_primitives::U256; + + let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234"; + let signer: PrivateKeySigner = private_key.parse().expect("Valid private key"); + + let result = create_l1_headers(&signer, Some(U256::from(12345))); + assert!(result.is_ok()); + + let headers = result.unwrap(); + assert!(headers.contains_key("POLY_ADDRESS")); + assert!(headers.contains_key("POLY_SIGNATURE")); + assert!(headers.contains_key("POLY_TIMESTAMP")); + assert!(headers.contains_key("POLY_NONCE")); + } + + #[test] + fn test_create_l1_headers_different_nonces() { + use alloy_signer_local::PrivateKeySigner; + use alloy_primitives::U256; + + let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234"; + let signer: PrivateKeySigner = private_key.parse().expect("Valid private key"); + + let headers_1 = create_l1_headers(&signer, Some(U256::from(12345))).unwrap(); + let headers_2 = create_l1_headers(&signer, Some(U256::from(54321))).unwrap(); + + // Different nonces should produce different signatures + assert_ne!( + headers_1.get("POLY_SIGNATURE"), + headers_2.get("POLY_SIGNATURE") + ); + + // But same address + assert_eq!( + headers_1.get("POLY_ADDRESS"), + headers_2.get("POLY_ADDRESS") + ); + } + + #[test] + fn test_create_l2_headers() { + use alloy_signer_local::PrivateKeySigner; + + let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234"; + let signer: PrivateKeySigner = private_key.parse().expect("Valid private key"); + + let api_creds = ApiCredentials { + api_key: "test_key".to_string(), + secret: "test_secret".to_string(), + passphrase: "test_passphrase".to_string(), + }; + + let result = create_l2_headers(&signer, &api_creds, "/test", "GET", None); + assert!(result.is_ok()); + + let headers = result.unwrap(); + assert!(headers.contains_key("POLY_API_KEY")); + assert!(headers.contains_key("POLY_SIGNATURE")); + assert!(headers.contains_key("POLY_TIMESTAMP")); + assert!(headers.contains_key("POLY_PASSPHRASE")); + + assert_eq!(headers.get("POLY_API_KEY").unwrap(), "test_key"); + assert_eq!(headers.get("POLY_PASSPHRASE").unwrap(), "test_passphrase"); + } + + #[test] + fn test_eip712_signature_format() { + use alloy_signer_local::PrivateKeySigner; + use alloy_primitives::U256; + + let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234"; + let signer: PrivateKeySigner = private_key.parse().expect("Valid private key"); + + // Test that we can create and sign EIP-712 messages + let result = create_l1_headers(&signer, Some(U256::from(12345))); + assert!(result.is_ok()); + + let headers = result.unwrap(); + let signature = headers.get("POLY_SIGNATURE").unwrap(); + + // EIP-712 signatures should be hex strings of specific length + assert!(signature.starts_with("0x")); + assert_eq!(signature.len(), 132); // 0x + 130 hex chars = 132 total + } + + #[test] + fn test_timestamp_generation() { + let ts1 = get_current_unix_time_secs(); + std::thread::sleep(std::time::Duration::from_millis(1)); + let ts2 = get_current_unix_time_secs(); + + // Timestamps should be increasing + assert!(ts2 >= ts1); + + // Should be reasonable current time (after 2020, before 2030) + assert!(ts1 > 1_600_000_000); + assert!(ts1 < 1_900_000_000); + } } diff --git a/src/client.rs b/src/client.rs index d9ec0f3..98e9fdd 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1178,4 +1178,459 @@ pub struct CreateOrderOptions { } // Re-export for compatibility -pub type PolyfillClient = ClobClient; \ No newline at end of file +pub type PolyfillClient = ClobClient; + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::*; + use mockito::{Matcher, Server}; + use rust_decimal::Decimal; + use std::str::FromStr; + use tokio; + + fn create_test_client(base_url: &str) -> ClobClient { + ClobClient::new(base_url) + } + + fn create_test_client_with_auth(base_url: &str) -> ClobClient { + ClobClient::with_l1_headers( + base_url, + "0x1234567890123456789012345678901234567890123456789012345678901234", + 137, + ) + } + + #[tokio::test] + async fn test_client_creation() { + let client = create_test_client("https://test.example.com"); + assert_eq!(client.base_url, "https://test.example.com"); + assert!(client.signer.is_none()); + assert!(client.api_creds.is_none()); + } + + #[tokio::test] + async fn test_client_with_l1_headers() { + let client = create_test_client_with_auth("https://test.example.com"); + assert_eq!(client.base_url, "https://test.example.com"); + assert!(client.signer.is_some()); + assert_eq!(client.chain_id, 137); + } + + #[tokio::test] + async fn test_client_with_l2_headers() { + let api_creds = ApiCreds { + key: "test_key".to_string(), + secret: "test_secret".to_string(), + passphrase: "test_passphrase".to_string(), + }; + + let client = ClobClient::with_l2_headers( + "https://test.example.com", + "0x1234567890123456789012345678901234567890123456789012345678901234", + 137, + api_creds.clone(), + ); + + assert_eq!(client.base_url, "https://test.example.com"); + assert!(client.signer.is_some()); + assert!(client.api_creds.is_some()); + assert_eq!(client.chain_id, 137); + } + + #[tokio::test] + async fn test_set_api_creds() { + let mut client = create_test_client("https://test.example.com"); + assert!(client.api_creds.is_none()); + + let api_creds = ApiCreds { + key: "test_key".to_string(), + secret: "test_secret".to_string(), + passphrase: "test_passphrase".to_string(), + }; + + client.set_api_creds(api_creds.clone()); + assert!(client.api_creds.is_some()); + assert_eq!(client.api_creds.unwrap().key, "test_key"); + } + + #[tokio::test] + async fn test_get_sampling_markets_success() { + let mut server = Server::new_async().await; + let mock_response = r#"{ + "limit": "10", + "count": "2", + "next_cursor": null, + "data": [ + { + "condition_id": "0x123", + "tokens": [ + {"token_id": "0x456", "outcome": "Yes"}, + {"token_id": "0x789", "outcome": "No"} + ], + "rewards": { + "rates": null, + "min_size": "1.0", + "max_spread": "0.1", + "event_start_date": null, + "event_end_date": null, + "in_game_multiplier": null, + "reward_epoch": null + }, + "min_incentive_size": null, + "max_incentive_spread": null, + "active": true, + "closed": false, + "question_id": "0x123", + "minimum_order_size": "1.0", + "minimum_tick_size": "0.01", + "description": "Test market", + "category": "test", + "end_date_iso": null, + "game_start_time": null, + "question": "Will this test pass?", + "market_slug": "test-market", + "seconds_delay": "0", + "icon": "", + "fpmm": "" + } + ] + }"#; + + let mock = server + .mock("GET", "/sampling-markets") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(mock_response) + .create_async() + .await; + + let client = create_test_client(&server.url()); + let result = client.get_sampling_markets(None).await; + + mock.assert_async().await; + assert!(result.is_ok()); + let markets = result.unwrap(); + assert_eq!(markets.data.len(), 1); + assert_eq!(markets.data[0].question, "Will this test pass?"); + } + + #[tokio::test] + async fn test_get_sampling_markets_with_cursor() { + let mut server = Server::new_async().await; + let mock_response = r#"{ + "limit": "5", + "count": "0", + "next_cursor": null, + "data": [] + }"#; + + let mock = server + .mock("GET", "/sampling-markets") + .match_query(Matcher::AllOf(vec![ + Matcher::UrlEncoded("cursor".into(), "test_cursor".into()), + ])) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(mock_response) + .create_async() + .await; + + let client = create_test_client(&server.url()); + let result = client.get_sampling_markets(Some("test_cursor")).await; + + mock.assert_async().await; + assert!(result.is_ok()); + let markets = result.unwrap(); + assert_eq!(markets.data.len(), 0); + } + + #[tokio::test] + async fn test_get_order_book_success() { + let mut server = Server::new_async().await; + let mock_response = r#"{ + "market": "0x123", + "bids": [ + {"price": "0.75", "size": "100.0"} + ], + "asks": [ + {"price": "0.76", "size": "50.0"} + ] + }"#; + + let mock = server + .mock("GET", "/book") + .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(mock_response) + .create_async() + .await; + + let client = create_test_client(&server.url()); + let result = client.get_order_book("0x123").await; + + mock.assert_async().await; + assert!(result.is_ok()); + let book = result.unwrap(); + assert_eq!(book.market, "0x123"); + assert_eq!(book.bids.len(), 1); + assert_eq!(book.asks.len(), 1); + } + + #[tokio::test] + async fn test_get_midpoint_success() { + let mut server = Server::new_async().await; + let mock_response = r#"{ + "mid": "0.755" + }"#; + + let mock = server + .mock("GET", "/midpoint") + .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(mock_response) + .create_async() + .await; + + let client = create_test_client(&server.url()); + let result = client.get_midpoint("0x123").await; + + mock.assert_async().await; + assert!(result.is_ok()); + let response = result.unwrap(); + assert_eq!(response.mid, Decimal::from_str("0.755").unwrap()); + } + + #[tokio::test] + async fn test_get_spread_success() { + let mut server = Server::new_async().await; + let mock_response = r#"{ + "spread": "0.01" + }"#; + + let mock = server + .mock("GET", "/spread") + .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(mock_response) + .create_async() + .await; + + let client = create_test_client(&server.url()); + let result = client.get_spread("0x123").await; + + mock.assert_async().await; + assert!(result.is_ok()); + let response = result.unwrap(); + assert_eq!(response.spread, Decimal::from_str("0.01").unwrap()); + } + + #[tokio::test] + async fn test_get_price_success() { + let mut server = Server::new_async().await; + let mock_response = r#"{ + "price": "0.76" + }"#; + + let mock = server + .mock("GET", "/price") + .match_query(Matcher::AllOf(vec![ + Matcher::UrlEncoded("token_id".into(), "0x123".into()), + Matcher::UrlEncoded("side".into(), "buy".into()), + ])) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(mock_response) + .create_async() + .await; + + let client = create_test_client(&server.url()); + let result = client.get_price("0x123", Side::BUY).await; + + mock.assert_async().await; + assert!(result.is_ok()); + let response = result.unwrap(); + assert_eq!(response.price, Decimal::from_str("0.76").unwrap()); + } + + #[tokio::test] + async fn test_get_tick_size_success() { + let mut server = Server::new_async().await; + let mock_response = r#"{ + "minimum_tick_size": "0.01" + }"#; + + let mock = server + .mock("GET", "/tick-size") + .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(mock_response) + .create_async() + .await; + + let client = create_test_client(&server.url()); + let result = client.get_tick_size("0x123").await; + + mock.assert_async().await; + assert!(result.is_ok()); + let tick_size = result.unwrap(); + assert_eq!(tick_size, Decimal::from_str("0.01").unwrap()); + } + + #[tokio::test] + async fn test_get_neg_risk_success() { + let mut server = Server::new_async().await; + let mock_response = r#"{ + "neg_risk": false + }"#; + + let mock = server + .mock("GET", "/neg-risk") + .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into())) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(mock_response) + .create_async() + .await; + + let client = create_test_client(&server.url()); + let result = client.get_neg_risk("0x123").await; + + mock.assert_async().await; + assert!(result.is_ok()); + let neg_risk = result.unwrap(); + assert!(!neg_risk); + } + + #[tokio::test] + async fn test_api_error_handling() { + let mut server = Server::new_async().await; + + let mock = server + .mock("GET", "/book") + .with_status(404) + .with_header("content-type", "application/json") + .with_body(r#"{"error": "Market not found"}"#) + .create_async() + .await; + + let client = create_test_client(&server.url()); + let result = client.get_order_book("invalid_token").await; + + mock.assert_async().await; + assert!(result.is_err()); + + let error = result.unwrap_err(); + assert!(matches!(error, PolyfillError::Network { .. })); + } + + #[tokio::test] + async fn test_network_error_handling() { + // Test with invalid URL to simulate network error + let client = create_test_client("http://invalid-host-that-does-not-exist.com"); + let result = client.get_order_book("0x123").await; + + assert!(result.is_err()); + let error = result.unwrap_err(); + assert!(matches!(error, PolyfillError::Network { .. })); + } + + #[test] + fn test_resolve_tick_size() { + let client = create_test_client("https://test.example.com"); + + // Test with provided tick size + let result = client.resolve_tick_size(Some(Decimal::from_str("0.001").unwrap())); + assert_eq!(result, Decimal::from_str("0.001").unwrap()); + + // Test with default tick size + let result = client.resolve_tick_size(None); + assert_eq!(result, Decimal::from_str("0.01").unwrap()); + } + + #[test] + fn test_is_price_in_range() { + let client = create_test_client("https://test.example.com"); + + let price = Decimal::from_str("0.5").unwrap(); + let tick_size = Decimal::from_str("0.01").unwrap(); + + // Test valid price + assert!(client.is_price_in_range(price, tick_size)); + + // Test price too low + let low_price = Decimal::from_str("0.005").unwrap(); + assert!(!client.is_price_in_range(low_price, tick_size)); + + // Test price too high + let high_price = Decimal::from_str("0.995").unwrap(); + assert!(!client.is_price_in_range(high_price, tick_size)); + } + + #[tokio::test] + async fn test_get_midpoints_batch() { + let mut server = Server::new_async().await; + let mock_response = r#"{ + "0x123": "0.755", + "0x456": "0.623" + }"#; + + let mock = server + .mock("GET", "/midpoints") + .match_query(Matcher::AllOf(vec![ + Matcher::UrlEncoded("token_ids".into(), "0x123,0x456".into()), + ])) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(mock_response) + .create_async() + .await; + + let client = create_test_client(&server.url()); + let token_ids = vec!["0x123".to_string(), "0x456".to_string()]; + let result = client.get_midpoints(&token_ids).await; + + mock.assert_async().await; + assert!(result.is_ok()); + let midpoints = result.unwrap(); + assert_eq!(midpoints.len(), 2); + assert_eq!(midpoints.get("0x123").unwrap(), &Decimal::from_str("0.755").unwrap()); + assert_eq!(midpoints.get("0x456").unwrap(), &Decimal::from_str("0.623").unwrap()); + } + + #[test] + fn test_calculate_market_price() { + let client = create_test_client("https://test.example.com"); + + // Test buy order + let buy_price = client.calculate_market_price( + Decimal::from_str("0.75").unwrap(), + Decimal::from_str("0.76").unwrap(), + Side::BUY, + Some(Decimal::from_str("0.02").unwrap()), + ); + assert_eq!(buy_price, Decimal::from_str("0.7752").unwrap()); // 0.76 * 1.02 + + // Test sell order + let sell_price = client.calculate_market_price( + Decimal::from_str("0.75").unwrap(), + Decimal::from_str("0.76").unwrap(), + Side::SELL, + Some(Decimal::from_str("0.02").unwrap()), + ); + assert_eq!(sell_price, Decimal::from_str("0.735").unwrap()); // 0.75 * 0.98 + + // Test without slippage + let no_slippage_buy = client.calculate_market_price( + Decimal::from_str("0.75").unwrap(), + Decimal::from_str("0.76").unwrap(), + Side::BUY, + None, + ); + assert_eq!(no_slippage_buy, Decimal::from_str("0.76").unwrap()); + } +} \ No newline at end of file diff --git a/src/orders.rs b/src/orders.rs index 844d0a2..acbeede 100644 --- a/src/orders.rs +++ b/src/orders.rs @@ -381,4 +381,218 @@ mod tests { let seed2 = generate_seed(); assert_ne!(seed1, seed2); } + + #[test] + fn test_decimal_to_token_u32_edge_cases() { + // Test zero + let result = decimal_to_token_u32(Decimal::ZERO); + assert_eq!(result, 0); + + // Test small decimal + let result = decimal_to_token_u32(Decimal::from_str("0.000001").unwrap()); + assert_eq!(result, 1); + + // Test large number + let result = decimal_to_token_u32(Decimal::from_str("1000.0").unwrap()); + assert_eq!(result, 1_000_000_000); + } + + #[tokio::test] + async fn test_order_builder_creation() { + use alloy_signer_local::PrivateKeySigner; + + let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234"; + let signer: PrivateKeySigner = private_key.parse().expect("Valid private key"); + + let builder = OrderBuilder::new(signer, 137); + assert_eq!(builder.chain_id, 137); + } + + #[tokio::test] + async fn test_build_order_success() { + use alloy_signer_local::PrivateKeySigner; + + let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234"; + let signer: PrivateKeySigner = private_key.parse().expect("Valid private key"); + + let mut builder = OrderBuilder::new(signer, 137); + + let order_args = crate::client::OrderArgs { + token_id: "0x123".to_string(), + price: Decimal::from_str("0.75").unwrap(), + size: Decimal::from_str("100.0").unwrap(), + side: Side::BUY, + }; + + let result = builder.build_order(&order_args).await; + assert!(result.is_ok()); + + let signed_order = result.unwrap(); + assert_eq!(signed_order.token_id, "0x123"); + assert!(!signed_order.signature.is_empty()); + } + + #[tokio::test] + async fn test_build_order_different_sides() { + use alloy_signer_local::PrivateKeySigner; + + let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234"; + let signer: PrivateKeySigner = private_key.parse().expect("Valid private key"); + + let mut builder = OrderBuilder::new(signer, 137); + + let buy_order = crate::client::OrderArgs { + token_id: "0x123".to_string(), + price: Decimal::from_str("0.75").unwrap(), + size: Decimal::from_str("100.0").unwrap(), + side: Side::BUY, + }; + + let sell_order = crate::client::OrderArgs { + token_id: "0x123".to_string(), + price: Decimal::from_str("0.75").unwrap(), + size: Decimal::from_str("100.0").unwrap(), + side: Side::SELL, + }; + + let buy_result = builder.build_order(&buy_order).await.unwrap(); + let sell_result = builder.build_order(&sell_order).await.unwrap(); + + // Different sides should produce different signatures + assert_ne!(buy_result.signature, sell_result.signature); + + // But same other fields + assert_eq!(buy_result.token_id, sell_result.token_id); + assert_eq!(buy_result.maker_amount, sell_result.taker_amount); + assert_eq!(buy_result.taker_amount, sell_result.maker_amount); + } + + #[tokio::test] + async fn test_build_order_price_rounding() { + use alloy_signer_local::PrivateKeySigner; + + let private_key = "0x1234567890123456789012345678901234567890123456789012345678901234"; + let signer: PrivateKeySigner = private_key.parse().expect("Valid private key"); + + let mut builder = OrderBuilder::new(signer, 137); + + // Test price that needs rounding + let order_args = crate::client::OrderArgs { + token_id: "0x123".to_string(), + price: Decimal::from_str("0.753456").unwrap(), // Should round to nearest tick + size: Decimal::from_str("100.0").unwrap(), + side: Side::BUY, + }; + + let result = builder.build_order(&order_args).await; + assert!(result.is_ok()); + + let signed_order = result.unwrap(); + // The price should be rounded (exact value depends on tick size) + assert!(!signed_order.maker_amount.is_empty()); + assert!(!signed_order.taker_amount.is_empty()); + } + + #[test] + fn test_round_to_tick_size() { + let tick_size = Decimal::from_str("0.01").unwrap(); + + // Test exact tick + let price = Decimal::from_str("0.75").unwrap(); + let rounded = round_to_tick_size(price, tick_size); + assert_eq!(rounded, price); + + // Test rounding down + let price = Decimal::from_str("0.754").unwrap(); + let rounded = round_to_tick_size(price, tick_size); + assert_eq!(rounded, Decimal::from_str("0.75").unwrap()); + + // Test rounding up + let price = Decimal::from_str("0.756").unwrap(); + let rounded = round_to_tick_size(price, tick_size); + assert_eq!(rounded, Decimal::from_str("0.76").unwrap()); + } + + #[test] + fn test_round_to_tick_size_different_tick_sizes() { + // Test with 0.001 tick size + let tick_size = Decimal::from_str("0.001").unwrap(); + let price = Decimal::from_str("0.7534").unwrap(); + let rounded = round_to_tick_size(price, tick_size); + assert_eq!(rounded, Decimal::from_str("0.753").unwrap()); + + // Test with 0.1 tick size + let tick_size = Decimal::from_str("0.1").unwrap(); + let price = Decimal::from_str("0.75").unwrap(); + let rounded = round_to_tick_size(price, tick_size); + assert_eq!(rounded, Decimal::from_str("0.8").unwrap()); + } + + #[test] + fn test_validate_order_args() { + // Test valid order + let valid_order = crate::client::OrderArgs { + token_id: "0x1234567890123456789012345678901234567890".to_string(), + price: Decimal::from_str("0.75").unwrap(), + size: Decimal::from_str("100.0").unwrap(), + side: Side::BUY, + }; + + let result = validate_order_args(&valid_order); + assert!(result.is_ok()); + + // Test invalid token ID + let invalid_token = crate::client::OrderArgs { + token_id: "invalid".to_string(), + price: Decimal::from_str("0.75").unwrap(), + size: Decimal::from_str("100.0").unwrap(), + side: Side::BUY, + }; + + let result = validate_order_args(&invalid_token); + assert!(result.is_err()); + + // Test zero price + let zero_price = crate::client::OrderArgs { + token_id: "0x1234567890123456789012345678901234567890".to_string(), + price: Decimal::ZERO, + size: Decimal::from_str("100.0").unwrap(), + side: Side::BUY, + }; + + let result = validate_order_args(&zero_price); + assert!(result.is_err()); + + // Test zero size + let zero_size = crate::client::OrderArgs { + token_id: "0x1234567890123456789012345678901234567890".to_string(), + price: Decimal::from_str("0.75").unwrap(), + size: Decimal::ZERO, + side: Side::BUY, + }; + + let result = validate_order_args(&zero_size); + assert!(result.is_err()); + } + + #[test] + fn test_seed_generation_uniqueness() { + let mut seeds = std::collections::HashSet::new(); + + // Generate 1000 seeds and ensure they're all unique + for _ in 0..1000 { + let seed = generate_seed(); + assert!(seeds.insert(seed), "Duplicate seed generated"); + } + } + + #[test] + fn test_seed_generation_range() { + for _ in 0..100 { + let seed = generate_seed(); + // Seeds should be positive and within reasonable range + assert!(seed > 0); + assert!(seed < u64::MAX); + } + } } diff --git a/tarpaulin-report.html b/tarpaulin-report.html new file mode 100644 index 0000000..d2a5388 --- /dev/null +++ b/tarpaulin-report.html @@ -0,0 +1,737 @@ + + + + + + + +
+ + + + + + \ No newline at end of file