use ib_flex::parse_activity_flex;
use proptest::prelude::*;
proptest! {
#[test]
fn test_parse_handles_any_valid_account_id(account_id in "[A-Z0-9]{8}") {
let xml = format!(r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Test" type="AF">
<FlexStatements count="1">
<FlexStatement accountId="{}" fromDate="2025-01-01"
toDate="2025-01-31" whenGenerated="2025-01-31;150000">
<Trades />
<OpenPositions />
<CashTransactions />
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#, account_id);
let result = parse_activity_flex(&xml);
assert!(result.is_ok());
assert_eq!(result.unwrap().account_id, account_id);
}
#[test]
fn test_parse_handles_various_date_ranges(
year in 2020u32..=2030u32,
month in 1u32..=12u32,
day in 1u32..=28u32 ) {
let from_date = format!("{:04}-{:02}-{:02}", year, month, day);
let to_date = format!("{:04}-{:02}-{:02}", year, month, day);
let xml = format!(r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Test" type="AF">
<FlexStatements count="1">
<FlexStatement accountId="U1234567" fromDate="{}"
toDate="{}" whenGenerated="2025-01-31;150000">
<Trades />
<OpenPositions />
<CashTransactions />
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#, from_date, to_date);
let result = parse_activity_flex(&xml);
prop_assert!(result.is_ok());
}
#[test]
fn test_parse_handles_various_decimal_values(
price in -1000000.0f64..1000000.0f64,
quantity in 0.001f64..1000000.0f64
) {
let price_str = format!("{:.2}", price);
let quantity_str = format!("{:.3}", quantity);
let xml = format!(r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Test" type="AF">
<FlexStatements count="1">
<FlexStatement accountId="U1234567" fromDate="2025-01-01"
toDate="2025-01-31" whenGenerated="2025-01-31;150000">
<Trades>
<Trade accountId="U1234567" conid="265598" symbol="TEST"
assetCategory="STK" tradeDate="2025-01-15"
settleDateTarget="2025-01-17" quantity="{}"
tradePrice="{}" proceeds="-15000.00"
ibCommission="-1.00" netCash="-15001.00"
currency="USD" buySell="BUY" />
</Trades>
<OpenPositions />
<CashTransactions />
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#, quantity_str, price_str);
let result = parse_activity_flex(&xml);
prop_assert!(result.is_ok());
}
}
#[test]
fn test_parse_large_number_of_trades() {
let mut trades = String::from("<Trades>");
for i in 0..1000 {
trades.push_str(&format!(
r#"<Trade accountId="U1234567" conid="{}" symbol="SYM{}"
assetCategory="STK" tradeDate="2025-01-{:02}"
settleDateTarget="2025-01-{:02}" quantity="100"
tradePrice="100.00" proceeds="-10000.00"
ibCommission="-1.00" netCash="-10001.00"
currency="USD" buySell="BUY" />"#,
265598 + i,
i,
(i % 28) + 1,
((i % 28) + 1 + 2) % 28 + 1
));
}
trades.push_str("</Trades>");
let xml = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Stress Test" type="AF">
<FlexStatements count="1">
<FlexStatement accountId="U1234567" fromDate="2025-01-01"
toDate="2025-01-31" whenGenerated="2025-01-31;150000">
{}
<OpenPositions />
<CashTransactions />
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#,
trades
);
let result = parse_activity_flex(&xml);
assert!(result.is_ok());
let statement = result.unwrap();
assert_eq!(statement.trades.items.len(), 1000);
}
#[test]
fn test_parse_large_number_of_positions() {
let mut positions = String::from("<OpenPositions>");
for i in 0..500 {
positions.push_str(&format!(
r#"<OpenPosition accountId="U1234567" conid="{}" symbol="POS{}"
assetCategory="STK" reportDate="2025-01-31"
position="100" markPrice="150.00" positionValue="15000.00"
currency="USD" />"#,
300000 + i,
i
));
}
positions.push_str("</OpenPositions>");
let xml = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Stress Test" type="AF">
<FlexStatements count="1">
<FlexStatement accountId="U1234567" fromDate="2025-01-01"
toDate="2025-01-31" whenGenerated="2025-01-31;150000">
<Trades />
{}
<CashTransactions />
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#,
positions
);
let result = parse_activity_flex(&xml);
if let Err(e) = &result {
eprintln!("Parse error: {:?}", e);
}
assert!(result.is_ok());
let statement = result.unwrap();
assert_eq!(statement.positions.items.len(), 500);
}
#[test]
fn test_parse_large_number_of_cash_transactions() {
let mut transactions = String::from("<CashTransactions>");
for i in 0..200 {
transactions.push_str(&format!(
r#"<CashTransaction accountId="U1234567" reportDate="2025-01-{:02}"
type="Deposits/Withdrawals" amount="1000.00"
currency="USD" />"#,
(i % 28) + 1
));
}
transactions.push_str("</CashTransactions>");
let xml = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Stress Test" type="AF">
<FlexStatements count="1">
<FlexStatement accountId="U1234567" fromDate="2025-01-01"
toDate="2025-01-31" whenGenerated="2025-01-31;150000">
<Trades />
<OpenPositions />
{}
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#,
transactions
);
let result = parse_activity_flex(&xml);
if let Err(e) = &result {
eprintln!("Parse error: {:?}", e);
}
assert!(result.is_ok());
let statement = result.unwrap();
assert_eq!(statement.cash_transactions.items.len(), 200);
}
#[test]
fn test_parse_mixed_large_sections() {
let mut trades = String::from("<Trades>");
for i in 0..100 {
trades.push_str(&format!(
r#"<Trade accountId="U1234567" conid="{}" symbol="T{}"
assetCategory="STK" tradeDate="2025-01-15"
settleDateTarget="2025-01-17" quantity="100"
tradePrice="100.00" proceeds="-10000.00"
ibCommission="-1.00" netCash="-10001.00"
currency="USD" buySell="BUY" />"#,
i, i
));
}
trades.push_str("</Trades>");
let mut positions = String::from("<OpenPositions>");
for i in 0..100 {
positions.push_str(&format!(
r#"<OpenPosition accountId="U1234567" conid="{}" symbol="P{}"
assetCategory="STK" reportDate="2025-01-31"
position="100" markPrice="150.00" positionValue="15000.00"
currency="USD" />"#,
i, i
));
}
positions.push_str("</OpenPositions>");
let mut cash = String::from("<CashTransactions>");
for _i in 0..100 {
cash.push_str(
r#"<CashTransaction accountId="U1234567" reportDate="2025-01-15"
type="Deposits/Withdrawals" amount="1000.00"
currency="USD" />"#,
);
}
cash.push_str("</CashTransactions>");
let xml = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Mixed Stress Test" type="AF">
<FlexStatements count="1">
<FlexStatement accountId="U1234567" fromDate="2025-01-01"
toDate="2025-01-31" whenGenerated="2025-01-31;150000">
{}
{}
{}
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#,
trades, positions, cash
);
let result = parse_activity_flex(&xml);
assert!(result.is_ok());
let statement = result.unwrap();
assert_eq!(statement.trades.items.len(), 100);
assert_eq!(statement.positions.items.len(), 100);
assert_eq!(statement.cash_transactions.items.len(), 100);
}
#[test]
fn test_parse_handles_extra_whitespace() {
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Test" type="AF" >
<FlexStatements count="1" >
<FlexStatement accountId="U1234567" fromDate="2025-01-01"
toDate="2025-01-31" whenGenerated="2025-01-31;150000" >
<Trades />
<OpenPositions />
<CashTransactions />
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#;
let result = parse_activity_flex(xml);
assert!(result.is_ok());
}
#[test]
fn test_parse_handles_varying_attribute_order() {
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse type="AF" queryName="Test">
<FlexStatements count="1">
<FlexStatement whenGenerated="2025-01-31;150000" toDate="2025-01-31"
fromDate="2025-01-01" accountId="U1234567">
<Trades />
<OpenPositions />
<CashTransactions />
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#;
let result = parse_activity_flex(xml);
assert!(result.is_ok());
}
#[test]
fn test_parse_handles_unicode_in_description() {
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Test" type="AF">
<FlexStatements count="1">
<FlexStatement accountId="U1234567" fromDate="2025-01-01"
toDate="2025-01-31" whenGenerated="2025-01-31;150000">
<Trades>
<Trade accountId="U1234567" conid="265598" symbol="AAPL"
description="Apple Inc. 🍎 Technology"
assetCategory="STK" tradeDate="2025-01-15"
settleDateTarget="2025-01-17" quantity="100"
tradePrice="150.00" proceeds="-15000.00"
ibCommission="-1.00" netCash="-15001.00"
currency="USD" buySell="BUY" />
</Trades>
<OpenPositions />
<CashTransactions />
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#;
let result = parse_activity_flex(xml);
assert!(result.is_ok());
}
#[test]
fn test_parse_handles_very_long_symbol() {
let long_symbol = "A".repeat(100);
let xml = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Test" type="AF">
<FlexStatements count="1">
<FlexStatement accountId="U1234567" fromDate="2025-01-01"
toDate="2025-01-31" whenGenerated="2025-01-31;150000">
<Trades>
<Trade accountId="U1234567" conid="265598" symbol="{}"
assetCategory="STK" tradeDate="2025-01-15"
settleDateTarget="2025-01-17" quantity="100"
tradePrice="150.00" proceeds="-15000.00"
ibCommission="-1.00" netCash="-15001.00"
currency="USD" buySell="BUY" />
</Trades>
<OpenPositions />
<CashTransactions />
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#,
long_symbol
);
let result = parse_activity_flex(&xml);
assert!(result.is_ok());
}
#[test]
fn test_parse_handles_all_sections_empty() {
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Empty Test" type="AF">
<FlexStatements count="1">
<FlexStatement accountId="U1234567" fromDate="2025-01-01"
toDate="2025-01-31" whenGenerated="2025-01-31;150000">
<Trades />
<OpenPositions />
<CashTransactions />
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#;
let result = parse_activity_flex(xml);
assert!(result.is_ok());
let statement = result.unwrap();
assert_eq!(statement.trades.items.len(), 0);
assert_eq!(statement.positions.items.len(), 0);
assert_eq!(statement.cash_transactions.items.len(), 0);
}
#[test]
fn test_parse_handles_extreme_decimal_precision() {
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Test" type="AF">
<FlexStatements count="1">
<FlexStatement accountId="U1234567" fromDate="2025-01-01"
toDate="2025-01-31" whenGenerated="2025-01-31;150000">
<Trades>
<Trade accountId="U1234567" conid="265598" symbol="TEST"
assetCategory="STK" tradeDate="2025-01-15"
settleDateTarget="2025-01-17"
quantity="0.00000001"
tradePrice="123456789.987654321"
proceeds="-15000.00"
ibCommission="-0.000001"
netCash="-15001.00"
currency="USD" buySell="BUY" />
</Trades>
<OpenPositions />
<CashTransactions />
<CorporateActions />
<SecuritiesInfo />
<ConversionRates />
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>"#;
let result = parse_activity_flex(xml);
assert!(result.is_ok());
}
#[test]
fn test_memory_efficiency_with_repeated_parsing() {
let xml = include_str!("fixtures/activity_minimal.xml");
for _ in 0..100 {
let result = parse_activity_flex(xml);
assert!(result.is_ok());
}
}
#[test]
fn test_concurrent_parsing_safety() {
use std::thread;
let xml = include_str!("fixtures/activity_minimal.xml").to_string();
let mut handles = vec![];
for _ in 0..10 {
let xml_clone = xml.clone();
let handle = thread::spawn(move || {
let result = parse_activity_flex(&xml_clone);
assert!(result.is_ok());
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}