113 lines
3.5 KiB
Python
113 lines
3.5 KiB
Python
from datetime import date
|
|
from decimal import Decimal
|
|
|
|
from sqlalchemy import inspect
|
|
from sqlalchemy.orm import Session
|
|
|
|
from src.models.category import CategoryBudgetDb, CategoryDb
|
|
from tests.factories import CategoryFactory
|
|
|
|
|
|
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=CategoryFactory.build().name)
|
|
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=CategoryFactory.build().name)
|
|
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=CategoryFactory.build().name)
|
|
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=CategoryFactory.build().name)
|
|
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]
|