Ledgerly/tests/test_reports_service.py
2026-10-27 11:04:50 -05:00

77 lines
2.7 KiB
Python

"""Tests for ledgerly.services.reports (R11)."""
from datetime import date
import pytest
from ledgerly.models import Month
from ledgerly.money import Money
from ledgerly.repository import SqliteRepository
from ledgerly.services import BudgetService, LedgerService, ReportingService
@pytest.fixture()
def services():
repo = SqliteRepository(":memory:")
yield LedgerService(repo), BudgetService(repo), ReportingService(repo)
repo.close()
def test_monthly_summary_empty(services):
_ledger, _budgets, reports = services
summary = reports.monthly_summary(Month(2026, 10))
assert summary.income == Money(0)
assert summary.expenses == Money(0)
assert summary.net == Money(0)
def test_monthly_summary_with_data(services):
ledger, _budgets, reports = services
ledger.add_transaction(Money(300000), "Salary", date(2026, 10, 1))
ledger.add_transaction(Money(50000), "Groceries", date(2026, 10, 5))
ledger.add_transaction(Money(1), "Rent", date(2026, 9, 5)) # out of month
summary = reports.monthly_summary(Month(2026, 10))
assert summary.income == Money(300000)
assert summary.expenses == Money(50000)
assert summary.net == Money(250000)
def test_category_totals_ordering(services):
ledger, _budgets, reports = services
ledger.add_transaction(Money(300000), "Salary", date(2026, 10, 1))
ledger.add_transaction(Money(50000), "Groceries", date(2026, 10, 2))
ledger.add_transaction(Money(90000), "Rent", date(2026, 10, 3))
totals = reports.category_totals(Month(2026, 10))
assert [(t.category, t.total) for t in totals] == [
("Salary", Money(300000)),
("Rent", Money(90000)),
("Groceries", Money(50000)),
]
def test_budget_status_budgeted_and_unbudgeted(services):
ledger, budgets, reports = services
budgets.set_budget("Rent", Money(150000))
ledger.add_transaction(Money(160000), "Rent", date(2026, 10, 1)) # over
ledger.add_transaction(Money(40000), "Groceries", date(2026, 10, 2)) # unbudgeted
lines = reports.budget_status(Month(2026, 10))
by_name = {line.category: line for line in lines}
assert by_name["Rent"].is_over
assert by_name["Rent"].remaining == Money(-10000)
assert by_name["Groceries"].budget == Money(0)
assert by_name["Groceries"].is_over
def test_budget_status_within_budget(services):
ledger, budgets, reports = services
budgets.set_budget("Rent", Money(150000))
ledger.add_transaction(Money(100000), "Rent", date(2026, 10, 1))
line = reports.budget_status(Month(2026, 10))[0]
assert not line.is_over
assert line.remaining == Money(50000)
def test_budget_status_empty(services):
_ledger, _budgets, reports = services
assert reports.budget_status(Month(2026, 10)) == []