"""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)) == []