use ib_flex::parse_activity_flex;
use ib_flex::types::common::{CashTransactionType, LevelOfDetail};
use ib_flex::types::TransferType;
use rust_decimal::Decimal;
use std::str::FromStr;
#[test]
fn wild_legacy_comma_separator() {
let xml = include_str!("fixtures/wild_legacy_separator.xml");
let statement = parse_activity_flex(xml).expect("legacy-separator statement should parse");
assert!(statement
.when_generated
.as_deref()
.unwrap_or("")
.contains("2017-02-01"));
let cash = &statement.cash_transactions.items;
assert_eq!(cash.len(), 1);
assert_eq!(cash[0].amount, Some(Decimal::from_str("5000.00").unwrap()));
assert_eq!(cash[0].description.as_deref(), Some("LEGACY DEPOSIT"));
}
#[test]
fn wild_stock_grant_activity() {
let xml = include_str!("fixtures/wild_stock_grant.xml");
let statement = parse_activity_flex(xml).expect("stock-grant statement should parse");
let grants = &statement.stock_grant_activities.items;
assert_eq!(grants.len(), 1, "stock grant activity should be captured");
let g = &grants[0];
assert_eq!(g.symbol.as_deref(), Some("IBKR"));
assert_eq!(g.vesting_date, date_opt("2026-06-12"));
assert_eq!(g.quantity, Some(Decimal::from_str("2.6454").unwrap()));
assert_eq!(g.value, Some(Decimal::from_str("541.01").unwrap()));
}
#[test]
fn wild_crypto_otc_transfer() {
let xml = include_str!("fixtures/wild_crypto_transfer.xml");
let statement = parse_activity_flex(xml).expect("crypto-transfer statement should parse");
let transfers = &statement.transfers.items;
assert_eq!(transfers.len(), 1);
let t = &transfers[0];
assert_eq!(t.transfer_type, Some(TransferType::OTC));
assert_eq!(t.symbol.as_deref(), Some("BTC.USD-PAXOS"));
assert_eq!(t.company, None);
}
fn date_opt(s: &str) -> Option<chrono::NaiveDate> {
Some(chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").unwrap())
}
#[test]
fn wild_lending_economics_and_analytics_fields() {
let xml = include_str!("fixtures/wild_lending_economics.xml");
let statement = parse_activity_flex(xml).expect("lending-economics statement should parse");
assert_eq!(statement.period.as_deref(), Some("LastNCalendarDays"));
let fees = &statement.slb_fees.items;
assert_eq!(fees.len(), 1);
let f = &fees[0];
assert_eq!(f.slb_type.as_deref(), Some("ManagedLoan"));
assert_eq!(f.gross_lend_fee, Some(Decimal::from_str("0.09").unwrap()));
assert_eq!(f.net_lend_fee, Some(Decimal::from_str("0.05").unwrap()));
assert_eq!(f.market_fee_rate, Some(Decimal::from_str("2.8").unwrap()));
assert_eq!(f.net_lend_fee_rate, Some(Decimal::from_str("1.4").unwrap()));
assert_eq!(f.figi.as_deref(), Some("BBG01F781N99"));
assert_eq!(f.isin.as_deref(), Some("CA44877L1013"));
assert_eq!(f.unique_id.as_deref(), Some("20250819109514782"));
assert_eq!(f.fee, Some(Decimal::ZERO));
assert_eq!(f.lending_income(), Some(Decimal::from_str("0.05").unwrap()));
assert_eq!(
statement.trades.items[0].level_of_detail,
Some(LevelOfDetail::Execution)
);
assert_eq!(
statement.trades.lots[0].level_of_detail,
Some(LevelOfDetail::ClosedLot)
);
assert_eq!(
statement.trades.wash_sales[0].level_of_detail,
Some(LevelOfDetail::WashSale)
);
assert_eq!(
statement.trades.orders[0].level_of_detail,
Some(LevelOfDetail::Order)
);
let cash = &statement.cash_transactions.items;
let kinds: Vec<_> = cash.iter().map(|c| c.transaction_type_kind()).collect();
assert!(kinds.contains(&Some(CashTransactionType::Dividends)));
assert!(kinds.contains(&Some(CashTransactionType::WithholdingTax)));
assert!(kinds.contains(&Some(CashTransactionType::PaymentInLieuOfDividends)));
assert!(cash
.iter()
.any(|c| c.transaction_type.as_deref() == Some("Withholding Tax")));
let acc = &statement.open_dividend_accruals.items[0];
assert_eq!(acc.action_id.as_deref(), Some("155097294"));
assert_eq!(acc.report_date, date_opt("2025-01-22"));
assert_eq!(acc.issuer_country_code.as_deref(), Some("US"));
assert!(cash
.iter()
.any(|c| c.action_id.as_deref() == acc.action_id.as_deref()));
let htb = &statement.hard_to_borrow_details.items[0];
assert_eq!(htb.figi.as_deref(), Some("BBG01F781N99"));
assert_eq!(htb.issuer_country_code.as_deref(), Some("CA"));
assert_eq!(statement.client_fees_detail.items.len(), 1);
assert_eq!(statement.transaction_taxes.details.len(), 1);
assert_eq!(
statement.transaction_taxes.details[0].tax_amount,
Some(Decimal::from_str("-5.00").unwrap())
);
assert_eq!(statement.transfers.lots.len(), 1);
assert_eq!(
statement.transfers.lots[0].transfer_price,
Some(Decimal::from_str("300.00").unwrap())
);
}