Ledgerly/tests/test_reports_service.py

77 lines
2.7 KiB
Python
Raw Permalink Normal View History

2026-10-27 16:04:50 +00:00
"""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)) == []