import unittest from decimal import Decimal from exceptions import MismatchedTradeError, TradeNotFound from ledger_action import LedgerAction from ledger_process import LedgerProcess class TestLedgerProcess(unittest.TestCase): def setUp(self): """Set up a LedgerProcess instance for testing.""" self.lp = LedgerProcess() def test_process_trade_valid(self): """Test valid processing of a trade with both EUR and crypto assets.""" eur_trade = LedgerAction( type="trade", asset="EUR", amount=Decimal("-500.00"), fee=Decimal("2.00"), timestamp="2025-04-17 10:00:00", refid="12345", ) crypto_trade = LedgerAction( type="trade", asset="BTC", amount=Decimal("0.1"), fee=Decimal("0.001"), timestamp="2025-04-17 10:00:00", refid="12345", ) self.lp.process_ledger([eur_trade, crypto_trade]) # Assert the remaining balance in the FIFO queue self.assertEqual( self.lp.fifo_queues["BTC"].get_remaining_amount(), Decimal("0.099") ) def test_empty_actions(self): """Test processing with no actions.""" self.lp.process_ledger([]) # with self.assertRaises(ValueError): def test_missing_trade_rows(self): """Test processing a trade with missing EUR or crypto rows.""" crypto_trade = LedgerAction( type="trade", asset="BTC", amount=Decimal("0.1"), fee=Decimal("0.001"), timestamp="2025-04-17 10:00:00", refid="12345", ) with self.assertRaises(MismatchedTradeError): self.lp.process_ledger([crypto_trade]) # EUR row missing def test_deposit_notfound(self): """Test handling deposit with no matching withdraw.""" deposit = LedgerAction( type="deposit", asset="BTC", amount=Decimal("1.00"), fee=Decimal("0.00"), timestamp="2025-04-17 10:00:00", refid="67890", ) with self.assertRaises(TradeNotFound): self.lp.process_ledger([deposit]) def test_deposit_before_trade(self): """Test depositing before the trade occurred. Can't work anyway b/c there's no matching withdraw. """ withdrawal = LedgerAction( type="withdrawal", asset="BTC", # already includes removed fee amount=Decimal("-0.098"), fee=Decimal("0.001"), timestamp="2025-04-18 12:00:00", refid="67890", ) deposit = LedgerAction( type="deposit", asset="BTC", amount=Decimal("0.098"), fee=Decimal("0.001"), timestamp="2024-04-18 12:00:00", refid="ABCDE", ) eur_trade = LedgerAction( type="trade", asset="EUR", amount=Decimal("-500.00"), fee=Decimal("2.00"), timestamp="2025-04-17 10:00:00", refid="12345", ) crypto_trade = LedgerAction( type="trade", asset="BTC", amount=Decimal("0.1"), fee=Decimal("0.001"), timestamp="2025-04-17 10:00:00", refid="12345", ) with self.assertRaises(TradeNotFound): self.lp.process_ledger([eur_trade, withdrawal, crypto_trade, deposit]) def test_withdraw_before_trade(self): """Test withdrawing before the trade occurred.""" withdrawal = LedgerAction( type="withdrawal", asset="BTC", # already includes removed fee amount=Decimal("-0.098"), fee=Decimal("0.001"), timestamp="2024-04-17 12:00:00", refid="67890", ) eur_trade = LedgerAction( type="trade", asset="EUR", amount=Decimal("-500.00"), fee=Decimal("2.00"), timestamp="2025-04-17 10:00:00", refid="12345", ) crypto_trade = LedgerAction( type="trade", asset="BTC", amount=Decimal("0.1"), fee=Decimal("0.001"), timestamp="2025-04-17 10:00:00", refid="12345", ) with self.assertRaises(TradeNotFound): self.lp.process_ledger([eur_trade, withdrawal, crypto_trade]) def test_withdraw_afterbalance(self): """Test withdrawing and subsequently checking balance.""" eur_trade = LedgerAction( type="trade", asset="EUR", amount=Decimal("-500.00"), fee=Decimal("2.00"), timestamp="2025-04-17 10:00:00", refid="12345", ) crypto_trade = LedgerAction( type="trade", asset="BTC", amount=Decimal("0.1"), fee=Decimal("0.001"), timestamp="2025-04-17 10:00:00", refid="12345", ) withdrawal = LedgerAction( type="withdrawal", asset="BTC", # already includes removed fee amount=Decimal("-0.098"), fee=Decimal("0.001"), timestamp="2025-04-17 12:00:00", refid="67890", ) self.lp.process_ledger([eur_trade, crypto_trade, withdrawal]) self.assertEqual(len(self.lp.fifo_queues["BTC"]), 0) self.assertEqual( self.lp.fifo_queues["BTC"].get_remaining_amount(), Decimal("0.0") ) self.assertEqual(len(self.lp.external_wallet["BTC"]), 1) self.assertEqual( self.lp.external_wallet["BTC"].get_remaining_amount(), Decimal("0.098") ) def test_withdraw_and_deposit_afterbalance(self): """Test withdrawing, then depositing, and subsequently checking balance.""" eur_trade = LedgerAction( type="trade", asset="EUR", amount=Decimal("-500.00"), fee=Decimal("2.00"), timestamp="2025-04-17 10:00:00", refid="12345", ) crypto_trade = LedgerAction( type="trade", asset="BTC", amount=Decimal("0.1"), fee=Decimal("0.001"), timestamp="2025-04-17 10:00:00", refid="12345", ) withdrawal = LedgerAction( type="withdrawal", asset="BTC", # already includes removed fee amount=Decimal("-0.098"), fee=Decimal("0.001"), timestamp="2025-04-17 12:00:00", refid="67890", ) # deposit: lists full amount first, then subtracts fee deposit = LedgerAction( type="deposit", asset="BTC", amount=Decimal("0.098"), fee=Decimal("0.001"), timestamp="2025-04-17 14:00:00", refid="55555", ) self.lp.process_ledger([eur_trade, crypto_trade, withdrawal, deposit]) self.assertEqual(len(self.lp.fifo_queues["BTC"]), 1) self.assertEqual( self.lp.fifo_queues["BTC"].get_remaining_amount(), Decimal("0.097") ) self.assertEqual(len(self.lp.external_wallet["BTC"]), 0) self.assertEqual( self.lp.external_wallet["BTC"].get_remaining_amount(), Decimal("0.0") ) if __name__ == "__main__": unittest.main()