PocketAccountant / tests /test_ledger.py
eldinosaur's picture
PocketAccountant: custom ledger UI + deterministic agent (engine, ledger, retrieval, classifier)
c55ab5e verified
Raw
History Blame Contribute Delete
6.12 kB
"""Tests for the double-entry ledger and its integration with the tax engine.
Run from the project root: python -m unittest discover -s tests
"""
import unittest
from decimal import Decimal
from src.engine import (
isr_provisional_monthly,
iva_monthly,
resico_isr_monthly,
taxable_base,
)
from src.ledger import Ledger, Line
def dec(s):
return Decimal(s)
class TestPosting(unittest.TestCase):
def setUp(self):
self.lg = Ledger(":memory:")
def tearDown(self):
self.lg.close()
def test_unbalanced_entry_rejected(self):
with self.assertRaises(ValueError):
self.lg.post(
"2024-05-01", "bad",
[Line("102.01", debit=100), Line("401.01", credit=50)],
)
def test_empty_entry_rejected(self):
with self.assertRaises(ValueError):
self.lg.post("2024-05-01", "empty", [Line("102.01", debit=0)])
def test_income_builds_balanced_entry(self):
self.lg.record_income("2024-05-10", "Diseño de logo", 45000, iva_rate="0.16")
# bank 52,200 ; income 45,000 ; iva trasladado 7,200
self.assertEqual(self.lg.account_balance("102.01"), dec("52200.00"))
self.assertEqual(self.lg.account_balance("401.01"), dec("45000.00"))
self.assertEqual(self.lg.account_balance("213.01"), dec("7200.00"))
def test_deductible_expense_splits_iva(self):
self.lg.record_expense("2024-05-15", "Licencia Adobe", 12000, iva_rate="0.16")
self.assertEqual(self.lg.account_balance("601.01"), dec("12000.00")) # expense
self.assertEqual(self.lg.account_balance("118.01"), dec("1920.00")) # iva acreditable
self.assertEqual(self.lg.account_balance("102.01"), dec("-13920.00")) # bank out
def test_income_with_retentions(self):
# Persona física → persona moral: 10% ISR + 10.6667% IVA retained
self.lg.record_income(
"2024-06-10", "Consultoría", 10000, iva_rate="0.16",
isr_retenido="1000", iva_retenido="1066.67",
)
self.assertEqual(self.lg.account_balance("102.01"), dec("9533.33")) # net cash
self.assertEqual(self.lg.account_balance("119.02"), dec("1000.00")) # isr retenido
self.assertEqual(self.lg.account_balance("119.01"), dec("1066.67")) # iva retenido
def test_zero_rated_income_has_no_iva(self):
self.lg.record_income("2024-05-20", "Export", 5000, iva_rate="0")
self.assertEqual(self.lg.account_balance("102.01"), dec("5000.00"))
self.assertEqual(self.lg.account_balance("213.01"), dec("0.00"))
def test_classifier_specific_expense_account(self):
self.lg.ensure_account("613.01", "Gastos de viaje", "expense", "debit")
self.lg.record_expense("2024-05-18", "Vuelo a cliente", 3000,
iva_rate="0.16", expense_code="613.01")
self.assertEqual(self.lg.account_balance("613.01"), dec("3000.00"))
class TestMonthTotals(unittest.TestCase):
def setUp(self):
self.lg = Ledger(":memory:")
self.lg.record_income("2024-05-10", "Diseño", 45000, iva_rate="0.16")
self.lg.record_expense("2024-05-15", "Adobe", 12000, iva_rate="0.16")
self.lg.record_expense("2024-05-20", "Multa", 500, iva_rate="0", deductible=False)
# noise in another month must not leak in
self.lg.record_income("2024-06-01", "Otro mes", 99999, iva_rate="0.16")
def tearDown(self):
self.lg.close()
def test_aggregates(self):
m = self.lg.month_totals(2024, 5)
self.assertEqual(m.income, dec("45000.00"))
self.assertEqual(m.deductible_expenses, dec("12000.00"))
self.assertEqual(m.nondeductible_expenses, dec("500.00"))
self.assertEqual(m.iva_trasladado, dec("7200.00"))
self.assertEqual(m.iva_acreditable, dec("1920.00"))
def test_month_isolation(self):
self.assertEqual(self.lg.month_totals(2024, 6).income, dec("99999.00"))
class TestStatements(unittest.TestCase):
def setUp(self):
self.lg = Ledger(":memory:")
self.lg.record_income("2024-05-10", "Diseño", 45000, iva_rate="0.16")
self.lg.record_expense("2024-05-15", "Adobe", 12000, iva_rate="0.16")
def tearDown(self):
self.lg.close()
def test_income_statement(self):
s = self.lg.income_statement(2024, 5)
self.assertEqual(s.revenue, dec("45000.00"))
self.assertEqual(s.expenses, dec("12000.00"))
self.assertEqual(s.net_profit, dec("33000.00"))
def test_balance_sheet_balances(self):
bs = self.lg.balance_sheet("2024-05-31")
# assets: bank 38,280 + iva acreditable 1,920 = 40,200
# liabilities: iva trasladado 7,200 ; equity = 33,000
self.assertEqual(bs.assets, dec("40200.00"))
self.assertEqual(bs.liabilities, dec("7200.00"))
self.assertEqual(bs.equity, dec("33000.00"))
# the accounting identity must hold
self.assertEqual(bs.assets, dec(str(bs.liabilities + bs.equity)))
class TestLedgerEngineIntegration(unittest.TestCase):
"""The whole point: ledger aggregates flow into the deterministic engine."""
def setUp(self):
self.lg = Ledger(":memory:")
self.lg.record_income("2024-05-10", "Diseño", 45000, iva_rate="0.16")
self.lg.record_expense("2024-05-15", "Adobe", 12000, iva_rate="0.16")
def tearDown(self):
self.lg.close()
def test_resico_from_ledger(self):
m = self.lg.month_totals(2024, 5)
self.assertEqual(resico_isr_monthly(m.income).amount, dec("495.00"))
def test_general_isr_from_ledger(self):
m = self.lg.month_totals(2024, 5)
base = taxable_base(m.income, m.deductible_expenses)
self.assertEqual(base, dec("33000"))
self.assertEqual(isr_provisional_monthly(base).amount, dec("5418.90"))
def test_iva_from_ledger(self):
m = self.lg.month_totals(2024, 5)
r = iva_monthly(m.iva_trasladado, m.iva_acreditable, m.iva_retenido)
self.assertEqual(r.amount, dec("5280.00"))
if __name__ == "__main__":
unittest.main()