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

125 lines
3.8 KiB
Python

"""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