from datetime import date from decimal import Decimal from sqlalchemy import inspect from sqlalchemy.orm import Session from src.models.b4w import CategoryDb from src.models.category_budget import CategoryBudgetDb def test_category_budget_schema(db_session: Session): inspector = inspect(db_session.get_bind()) columns = {c["name"]: c for c in inspector.get_columns("category_budget")} assert set(columns) == { "id", "category_id", "amount_eur", "valid_from", "valid_to", } assert columns["id"]["nullable"] is False assert columns["category_id"]["nullable"] is False assert columns["amount_eur"]["nullable"] is False assert columns["valid_from"]["nullable"] is True assert columns["valid_to"]["nullable"] is True assert str(columns["amount_eur"]["type"]) == "NUMERIC(12, 2)" foreign_keys = inspector.get_foreign_keys("category_budget") assert len(foreign_keys) == 1 assert foreign_keys[0]["constrained_columns"] == ["category_id"] assert foreign_keys[0]["referred_table"] == "category" def test_category_budget_defaults_null_dates(db_session: Session): category = CategoryDb(name="Lebenshaltung") db_session.add(category) db_session.flush() budget = CategoryBudgetDb(category_id=category.id, amount_eur=Decimal("150.00")) db_session.add(budget) db_session.commit() db_session.refresh(budget) assert budget.id is not None assert budget.category_id == category.id assert budget.amount_eur == Decimal("150.00") # Null means unbounded in either direction. assert budget.valid_from is None assert budget.valid_to is None def test_category_budget_date_window(db_session: Session): category = CategoryDb(name="Freizeit") db_session.add(category) db_session.flush() budget = CategoryBudgetDb( category_id=category.id, amount_eur=Decimal("200.50"), valid_from=date(2026, 10, 1), valid_to=date(2026, 10, 31), ) db_session.add(budget) db_session.commit() db_session.refresh(budget) assert budget.valid_from == date(2026, 10, 1) assert budget.valid_to == date(2026, 10, 31) def test_category_budget_relationship_both_ways(db_session: Session): category = CategoryDb(name="Beruf") db_session.add(category) db_session.flush() budget = CategoryBudgetDb(category_id=category.id, amount_eur=Decimal("1.00")) db_session.add(budget) db_session.commit() # Force re-read from the database so the relationship is loaded, not # just resolved from the session identity map. db_session.expire_all() assert [b.id for b in category.budgets] == [budget.id] assert budget.category.id == category.id def test_category_has_multiple_budgets(db_session: Session): category = CategoryDb(name="Wohnen") db_session.add(category) db_session.flush() budgets = [ CategoryBudgetDb( category_id=category.id, amount_eur=Decimal("1200.00"), valid_from=date(2026, 10, 1), valid_to=date(2026, 10, 31), ), CategoryBudgetDb( category_id=category.id, amount_eur=Decimal("1300.00"), valid_from=date(2026, 11, 1), valid_to=date(2026, 11, 30), ), ] for budget in budgets: db_session.add(budget) db_session.commit() db_session.expire_all() assert [b.id for b in category.budgets] == [b.id for b in budgets]