Ledgerly/tests/test_models.py

121 lines
3 KiB
Python
Raw Permalink Normal View History

2026-10-27 16:04:50 +00:00
"""Tests for ledgerly.models (sanitizers, Month, read models)."""
from dataclasses import FrozenInstanceError
from datetime import date
import pytest
from ledgerly.errors import ValidationError
from ledgerly.models import (
BudgetLine,
Category,
Month,
MonthSummary,
clean_name,
clean_note,
parse_date,
validate_kind,
)
from ledgerly.money import Money
def test_clean_name_ok():
assert clean_name(" Rent ") == "Rent"
def test_clean_name_rejects_blank():
with pytest.raises(ValidationError):
clean_name(" ")
with pytest.raises(ValidationError):
clean_name(None)
def test_clean_name_rejects_long():
with pytest.raises(ValidationError, match="50"):
clean_name("x" * 51)
def test_clean_note():
assert clean_note(None) is None
assert clean_note(" ") is None
assert clean_note("a note") == "a note"
def test_clean_note_rejects_long():
with pytest.raises(ValidationError, match="200"):
clean_note("n" * 201)
def test_parse_date_ok():
assert parse_date("2026-10-26") == date(2026, 10, 26)
def test_parse_date_rejects_bad():
for bad in ("2026-13-01", "2026-02-30", "25-10-01", "not-a-date", 123):
with pytest.raises(ValidationError):
parse_date(bad)
def test_parse_date_rejects_out_of_range():
with pytest.raises(ValidationError):
parse_date("1999-12-31")
with pytest.raises(ValidationError):
parse_date("2101-01-01")
def test_validate_kind():
assert validate_kind("income") == "income"
with pytest.raises(ValidationError):
validate_kind("asset")
def test_month_from_str_ok():
month = Month.from_str("2026-10")
assert (month.year, month.month) == (2026, 10)
assert month.label == "2026-10"
assert month.first_day == date(2026, 10, 1)
assert month.last_day == date(2026, 10, 31)
def test_month_from_str_rejects_bad():
for bad in ("2026", "26-10", "2026-13", "2026-", "2026-1-3", "garbage", 2026):
with pytest.raises(ValidationError):
Month.from_str(bad)
def test_month_constructor_validation():
with pytest.raises(ValidationError):
Month(2026, 13)
with pytest.raises(ValidationError):
Month(1999, 5)
def test_month_range_leap_year():
month = Month(year=2024, month=2)
assert month.range == ("2024-02-01", "2024-02-29")
def test_month_now_is_sane():
month = Month.now()
assert 1 <= month.month <= 12
def test_month_summary_net():
summary = MonthSummary(month=Month.now(), income=Money(3000), expenses=Money(3100))
assert summary.net == Money(-100)
def test_budget_line_over_and_under():
over = BudgetLine(category="Rent", budget=Money(1000), spent=Money(1200))
assert over.is_over
assert over.remaining == Money(-200)
under = BudgetLine(category="Rent", budget=Money(1000), spent=Money(800))
assert not under.is_over
assert under.remaining == Money(200)
def test_entities_are_frozen():
cat = Category(name="Rent", kind="expense")
with pytest.raises(FrozenInstanceError):
cat.name = "Other"