expenses_manager/expenses_manager/expenses/tests/test_accounts.py
JKuijperM e65b15ccf8 Extrae helpers de vistas y rehace los listados de cuentas y categorias
Saca de home() dos ayudantes reutilizables:

- _account_balances(): calcula el saldo de cada cuenta con dos queries
  agregadas en lugar de llamar a account.current_balance() en bucle,
  que disparaba un N+1.
- _category_tree(): aplana las categorias en preorden con su profundidad,
  para poder indentarlas en la plantilla.

Con ellos, account_list pasa a mostrar el saldo calculado de cada cuenta
(antes usaba current_balance() desde la plantilla) y category_list muestra
un arbol indentado en vez de una columna "Categoria padre" que obligaba a
reconstruir la jerarquia mentalmente.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 15:34:37 +02:00

102 lines
4.7 KiB
Python

import pytest
from datetime import date
from decimal import Decimal
from django.urls import reverse
from expenses.models import Account, Expense, Income, Category
pytestmark = pytest.mark.django_db
def test_current_balance_reflects_income_and_expenses(user, category):
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("100"))
Income.objects.create(owner=user, account=acc, name="Nomina", amount=Decimal("50"), date=date.today())
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("30"), date=date.today())
assert acc.current_balance() == Decimal("120")
def test_monthly_balance_returns_12_rows_in_order(user):
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("100"))
data = acc.monthly_balance(2020)
assert len(data) == 12
assert [row["month"] for row in data] == list(range(1, 13))
assert all(row["balance"] == 100.0 for row in data)
def test_monthly_balance_is_cumulative_across_months(user, category):
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("0"))
year = date.today().year - 1 # avoid the current-month live-balance patch
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("40"), date=date(year, 3, 1))
Income.objects.create(owner=user, account=acc, name="Extra", amount=Decimal("100"), date=date(year, 6, 1))
data = acc.monthly_balance(year)
assert data[2]["balance"] == -40.0 # month 3
assert data[5]["balance"] == 60.0 # month 6, cumulative
assert data[11]["balance"] == 60.0 # carries forward to December
def test_monthly_balance_respects_year_param_and_excludes_other_years(user, category):
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("0"))
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("50"), date=date(2023, 6, 15))
Income.objects.create(owner=user, account=acc, name="Extra", amount=Decimal("30"), date=date(2024, 3, 10))
data_2023 = acc.monthly_balance(2023)
assert data_2023[5]["balance"] == -50.0 # month 6
assert data_2023[11]["balance"] == -50.0
data_2024 = acc.monthly_balance(2024)
assert data_2024[1]["balance"] == -50.0 # month 2: prior year's expense folded into start
assert data_2024[2]["balance"] == -20.0 # month 3: 2024 income applied
def test_monthly_balance_current_month_patched_with_live_balance(user, category):
today = date.today()
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("100"))
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("30"), date=today)
data = acc.monthly_balance(today.year)
assert data[today.month - 1]["balance"] == float(acc.current_balance())
def test_monthly_net_returns_income_minus_expense_per_month(user, category):
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("0"))
Income.objects.create(owner=user, account=acc, name="Nomina", amount=Decimal("100"), date=date(2024, 4, 5))
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("40"), date=date(2024, 4, 20))
data = acc.monthly_net(2024)
assert len(data) == 12
assert data[3]["net"] == 60.0 # month 4
assert data[0]["net"] == 0.0
def test_balance_until_only_counts_entries_on_or_before_date(user, category):
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("0"))
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("20"), date=date(2024, 1, 10))
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("5"), date=date(2024, 1, 15))
Income.objects.create(owner=user, account=acc, name="Later", amount=Decimal("50"), date=date(2024, 1, 20))
assert acc.balance_until(date(2024, 1, 15)) == Decimal("-25")
def test_account_list_shows_all_accounts_with_computed_balances(auth_client, user, category, django_assert_max_num_queries):
active = Account.objects.create(owner=user, name="Activa", initial_balance=Decimal("100"))
Account.objects.create(owner=user, name="Inactiva", initial_balance=Decimal("50"), active=False)
Income.objects.create(owner=user, account=active, name="Nomina", amount=Decimal("20"), date=date.today())
Expense.objects.create(owner=user, account=active, category=category, amount=Decimal("5"), date=date.today())
with django_assert_max_num_queries(10):
response = auth_client.get(reverse('account_list'))
assert response.status_code == 200
rows = {row["account"].name: row["balance"] for row in response.context["account_rows"]}
assert rows == {"Activa": Decimal("115"), "Inactiva": Decimal("50")}