expenses_manager/expenses_manager/expenses/tests/test_goals.py

197 lines
7.4 KiB
Python

import pytest
from datetime import date
from decimal import Decimal
from expenses.models import Account, Category, Expense, Goal
from expenses.forms import GoalForm
pytestmark = pytest.mark.django_db
def _last_month_date():
today = date.today()
if today.month == 1:
return date(today.year - 1, 12, 15)
return date(today.year, today.month - 1, 15)
def test_payment_goal_sums_expenses_since_start_date(user, account, category):
start = date(2024, 6, 1)
Expense.objects.create(owner=user, account=account, category=category, amount=Decimal("10"), date=date(2024, 5, 1))
Expense.objects.create(owner=user, account=account, category=category, amount=Decimal("20"), date=date(2024, 6, 15))
goal = Goal.objects.create(
owner=user, name="Pago", kind=Goal.KIND_PAYMENT, category=category,
include_subcategories=False, start_date=start, target_amount=Decimal("100"),
)
assert goal.progress() == Decimal("20")
def test_payment_goal_include_subcategories_true_includes_child_expenses(user, account, category):
child = Category.objects.create(owner=user, name="Sub", parent=category)
Expense.objects.create(owner=user, account=account, category=child, amount=Decimal("15"), date=date.today())
goal = Goal.objects.create(
owner=user, name="Pago", kind=Goal.KIND_PAYMENT, category=category,
include_subcategories=True, target_amount=Decimal("100"),
)
assert goal.progress() == Decimal("15")
def test_payment_goal_include_subcategories_false_excludes_child_expenses(user, account, category):
child = Category.objects.create(owner=user, name="Sub", parent=category)
Expense.objects.create(owner=user, account=account, category=child, amount=Decimal("15"), date=date.today())
goal = Goal.objects.create(
owner=user, name="Pago", kind=Goal.KIND_PAYMENT, category=category,
include_subcategories=False, target_amount=Decimal("100"),
)
assert goal.progress() == Decimal("0")
def test_budget_goal_period_month_counts_only_current_month(user, account, category):
Expense.objects.create(owner=user, account=account, category=category, amount=Decimal("30"), date=date.today())
Expense.objects.create(owner=user, account=account, category=category, amount=Decimal("999"), date=_last_month_date())
goal = Goal.objects.create(
owner=user, name="Presupuesto", kind=Goal.KIND_BUDGET, category=category,
period=Goal.PERIOD_MONTH, target_amount=Decimal("100"),
)
assert goal.progress() == Decimal("30")
def test_budget_goal_is_exceeded_true_over_target(user, account, category):
Expense.objects.create(owner=user, account=account, category=category, amount=Decimal("150"), date=date.today())
goal = Goal.objects.create(
owner=user, name="Presupuesto", kind=Goal.KIND_BUDGET, category=category,
period=Goal.PERIOD_MONTH, target_amount=Decimal("100"),
)
assert goal.is_exceeded() is True
def test_budget_goal_is_exceeded_false_under_target(user, account, category):
Expense.objects.create(owner=user, account=account, category=category, amount=Decimal("50"), date=date.today())
goal = Goal.objects.create(
owner=user, name="Presupuesto", kind=Goal.KIND_BUDGET, category=category,
period=Goal.PERIOD_MONTH, target_amount=Decimal("100"),
)
assert goal.is_exceeded() is False
def test_is_exceeded_always_false_for_non_budget_kind(user, account, category):
Expense.objects.create(owner=user, account=account, category=category, amount=Decimal("50"), date=date.today())
goal = Goal.objects.create(
owner=user, name="Pago", kind=Goal.KIND_PAYMENT, category=category, target_amount=Decimal("10"),
)
assert goal.progress() > goal.target_amount
assert goal.is_exceeded() is False
def test_saving_goal_progress_equals_account_balance(user):
acc = Account.objects.create(owner=user, name="Ahorros", initial_balance=Decimal("500"))
goal = Goal.objects.create(
owner=user, name="Ahorro", kind=Goal.KIND_SAVING, account=acc, target_amount=Decimal("1000"),
)
assert goal.progress() == acc.current_balance()
def test_bar_width_caps_at_100_above_target(user):
acc = Account.objects.create(owner=user, name="Ahorros", initial_balance=Decimal("250"))
goal = Goal.objects.create(owner=user, name="Ahorro", kind=Goal.KIND_SAVING, account=acc, target_amount=Decimal("100"))
assert goal.percentage() == Decimal("250")
assert goal.bar_width() == 100.0
def test_bar_width_uncapped_below_100(user):
acc = Account.objects.create(owner=user, name="Ahorros", initial_balance=Decimal("40"))
goal = Goal.objects.create(owner=user, name="Ahorro", kind=Goal.KIND_SAVING, account=acc, target_amount=Decimal("100"))
assert goal.bar_width() == 40.0
@pytest.mark.parametrize("amount,expected_state", [
(Decimal("50"), "ok"),
(Decimal("90"), "warning"),
(Decimal("150"), "danger"),
])
def test_progress_state_budget_thresholds(user, account, category, amount, expected_state):
Expense.objects.create(owner=user, account=account, category=category, amount=amount, date=date.today())
goal = Goal.objects.create(
owner=user, name="Presupuesto", kind=Goal.KIND_BUDGET, category=category, target_amount=Decimal("100"),
)
assert goal.progress_state() == expected_state
@pytest.mark.parametrize("amount,expected_state", [
(Decimal("50"), "ok"),
(Decimal("100"), "complete"),
(Decimal("150"), "complete"),
])
def test_progress_state_non_budget_thresholds(user, account, category, amount, expected_state):
Expense.objects.create(owner=user, account=account, category=category, amount=amount, date=date.today())
goal = Goal.objects.create(
owner=user, name="Pago", kind=Goal.KIND_PAYMENT, category=category, target_amount=Decimal("100"),
)
assert goal.progress_state() == expected_state
def test_goalform_budget_requires_period(user, category):
data = {"name": "Presupuesto", "kind": Goal.KIND_BUDGET, "target_amount": "100", "category": str(category.pk), "period": Goal.PERIOD_NONE}
form = GoalForm(data=data, user=user)
assert not form.is_valid()
assert "period" in form.errors
def test_goalform_saving_requires_account(user):
data = {"name": "Ahorro", "kind": Goal.KIND_SAVING, "target_amount": "500", "period": Goal.PERIOD_NONE}
form = GoalForm(data=data, user=user)
assert not form.is_valid()
assert "account" in form.errors
def test_goalform_payment_requires_category(user):
data = {"name": "Pago", "kind": Goal.KIND_PAYMENT, "target_amount": "50", "period": Goal.PERIOD_NONE}
form = GoalForm(data=data, user=user)
assert not form.is_valid()
assert "category" in form.errors
def test_goalform_budget_requires_category(user):
data = {"name": "Presupuesto", "kind": Goal.KIND_BUDGET, "target_amount": "50", "period": Goal.PERIOD_MONTH}
form = GoalForm(data=data, user=user)
assert not form.is_valid()
assert "category" in form.errors
def test_goalform_valid_payment_saves_goal(user, category):
data = {
"name": "Ahorro viaje", "kind": Goal.KIND_PAYMENT, "target_amount": "200",
"category": str(category.pk), "period": Goal.PERIOD_NONE,
}
form = GoalForm(data=data, user=user)
assert form.is_valid(), form.errors
goal = form.save(commit=False)
goal.owner = user
goal.save()
assert Goal.objects.filter(pk=goal.pk, name="Ahorro viaje", kind=Goal.KIND_PAYMENT, category=category).exists()