197 lines
7.4 KiB
Python
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()
|