"""Tests for ledgerly.services.ledger (R5-R10, R12).""" from datetime import date import pytest from ledgerly.errors import ConflictError, NotFoundError, ValidationError from ledgerly.models import EXPENSE, INCOME, Month from ledgerly.money import Money from ledgerly.repository import SqliteRepository from ledgerly.services import LedgerService @pytest.fixture() def service(): repo = SqliteRepository(":memory:") yield LedgerService(repo) repo.close() # -- commands ------------------------------------------------------------------ def test_add_category(service): cat = service.add_category("Pets", EXPENSE) assert cat.name == "Pets" assert cat.kind == EXPENSE assert "Pets" in [c.name for c in service.list_categories()] def test_add_category_duplicate(service): service.add_category("Pets", EXPENSE) with pytest.raises(ConflictError): service.add_category("pets", EXPENSE) def test_add_category_invalid_name(service): with pytest.raises(ValidationError): service.add_category(" ", EXPENSE) def test_add_category_invalid_kind(service): with pytest.raises(ValidationError): service.add_category("Pets", "asset") def test_delete_category_missing(service): with pytest.raises(NotFoundError): service.delete_category("Nope") def test_delete_category_in_use(service): service.add_transaction(Money(100), "Rent", date(2026, 10, 1)) with pytest.raises(ConflictError, match="transactions or a budget"): service.delete_category("Rent") def test_delete_category_ok(service): service.delete_category("Rent") assert all(c.name != "Rent" for c in service.list_categories()) def test_add_transaction_ok(service): tx = service.add_transaction(Money(8210), "Groceries", date(2026, 10, 2), " weekly ") assert tx.id >= 1 assert tx.category == "Groceries" assert tx.kind == EXPENSE assert tx.note == "weekly" def test_add_transaction_rejects_zero(service): with pytest.raises(ValidationError, match="greater than zero"): service.add_transaction(Money(0), "Rent", date(2026, 10, 1)) def test_add_transaction_unknown_category(service): with pytest.raises(NotFoundError): service.add_transaction(Money(10), "Nope", date(2026, 10, 1)) def test_add_transaction_long_note(service): with pytest.raises(ValidationError, match="200"): service.add_transaction(Money(10), "Rent", date(2026, 10, 1), "x" * 201) def test_delete_transaction_missing(service): with pytest.raises(NotFoundError): service.delete_transaction(9999) def test_delete_transaction_ok(service): tx = service.add_transaction(Money(100), "Rent", date(2026, 10, 1)) service.delete_transaction(tx.id) assert service.list_transactions(None, None) == [] # -- queries ------------------------------------------------------------------- def test_list_transactions_filter_by_month(service): service.add_transaction(Money(100), "Rent", date(2026, 10, 1)) service.add_transaction(Money(200), "Rent", date(2026, 11, 1)) rows = service.list_transactions(*Month(2026, 10).range) assert len(rows) == 1 assert rows[0].amount == Money(100) def test_list_transactions_filter_by_category(service): service.add_transaction(Money(100), "Rent", date(2026, 10, 1)) service.add_transaction(Money(200), "Groceries", date(2026, 10, 2)) rows = service.list_transactions(None, None, "rent") assert len(rows) == 1 assert rows[0].category == "Rent" assert rows[0].kind == EXPENSE def test_list_transactions_unknown_category(service): with pytest.raises(NotFoundError): service.list_transactions(None, None, "Nope") def test_income_transaction_allowed(service): tx = service.add_transaction(Money(300000), "Salary", date(2026, 10, 1)) assert tx.kind == INCOME