mirror of
https://github.com/floor-licker/polyfill-rs.git
synced 2026-08-16 22:18:07 +00:00
tests: unit tests for client.rs and auth.rs
This commit is contained in:
+147
@@ -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::<String>(secret, timestamp, method, path, None).unwrap();
|
||||
let sig2 = build_hmac_signature::<String>(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::<String>(secret, timestamp, "GET", "/test", None).unwrap();
|
||||
let sig2 = build_hmac_signature::<String>(secret, timestamp, "POST", "/test", None).unwrap();
|
||||
let sig3 = build_hmac_signature::<String>(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);
|
||||
}
|
||||
}
|
||||
|
||||
+456
-1
@@ -1178,4 +1178,459 @@ pub struct CreateOrderOptions {
|
||||
}
|
||||
|
||||
// Re-export for compatibility
|
||||
pub type PolyfillClient = ClobClient;
|
||||
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());
|
||||
}
|
||||
}
|
||||
+214
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user