finalized helper functions for category trees; started writing tests
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -12,24 +13,30 @@ class TestCategoryComputedFields:
|
||||
def test_hash_id_computed_field_simple(self):
|
||||
"""Test hash_id for single-level category."""
|
||||
c = Category.from_str("single")
|
||||
assert c
|
||||
assert hasattr(c, "hash_id")
|
||||
assert isinstance(c.hash_id, str)
|
||||
# Hash should be deterministic for same inputs
|
||||
c2 = Category.from_str("single")
|
||||
assert c2
|
||||
assert c.hash_id == c2.hash_id
|
||||
|
||||
def test_hash_id_computed_field_nested(self):
|
||||
"""Test hash_id for multi-level category hierarchy."""
|
||||
c1 = Category.from_str("level1:level2:level3")
|
||||
assert c1
|
||||
assert hasattr(c1, "hash_id")
|
||||
|
||||
c2 = Category.from_str("level1:level2:level3")
|
||||
assert c2
|
||||
assert c1.hash_id == c2.hash_id
|
||||
|
||||
def test_hash_id_different_hierarchy(self):
|
||||
"""Test that different hierarchies have different hash_ids."""
|
||||
c1 = Category.from_str("A:B")
|
||||
c2 = Category.from_str("A:C")
|
||||
assert c1
|
||||
assert c2
|
||||
# Different last level -> different hash_id
|
||||
assert c1.hash_id != c2.hash_id
|
||||
|
||||
@@ -40,22 +47,26 @@ class TestCategoryComputedFields:
|
||||
|
||||
# Create another with same name but no parent
|
||||
orphan = Category.from_str("Budget")
|
||||
assert orphan
|
||||
|
||||
assert cat1.hash_id != orphan.hash_id
|
||||
|
||||
def test_to_str_single_level(self):
|
||||
"""Test to_str() for single-level category."""
|
||||
c = Category.from_str("single")
|
||||
assert c
|
||||
assert c.to_str() == "single"
|
||||
|
||||
def test_to_str_nested(self):
|
||||
"""Test to_str() for nested category."""
|
||||
c = Category.from_str("parent:child:grandchild")
|
||||
assert c
|
||||
assert c.to_str() == "parent:child:grandchild"
|
||||
|
||||
def test_str_uses_to_str(self):
|
||||
"""Test that __str__ delegates to to_str()."""
|
||||
c = Category.from_str("A:B:C")
|
||||
assert c
|
||||
assert str(c) == c.to_str() == "A:B:C"
|
||||
|
||||
def test_repr_simple(self):
|
||||
@@ -79,7 +90,7 @@ class TestCategoryBudgetValidation:
|
||||
cat = CategoryFactory.build()
|
||||
budget = CategoryBudget(
|
||||
category=cat,
|
||||
amount_eur=100.00,
|
||||
amount_eur=Decimal("100.00"),
|
||||
)
|
||||
assert budget.valid_from is None
|
||||
assert budget.valid_to is None
|
||||
@@ -90,7 +101,7 @@ class TestCategoryBudgetValidation:
|
||||
cat = CategoryFactory.build()
|
||||
budget = CategoryBudget(
|
||||
category=cat,
|
||||
amount_eur=100.00,
|
||||
amount_eur=Decimal("100.00"),
|
||||
valid_from=date(2026, 10, 1),
|
||||
valid_to=date(2026, 10, 31),
|
||||
)
|
||||
@@ -105,7 +116,7 @@ class TestCategoryBudgetValidation:
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
CategoryBudget(
|
||||
category=cat,
|
||||
amount_eur=100.00,
|
||||
amount_eur=Decimal("100.00"),
|
||||
valid_from=date(2026, 10, 31),
|
||||
valid_to=date(2026, 10, 1),
|
||||
)
|
||||
@@ -120,7 +131,7 @@ class TestCategoryBudgetValidation:
|
||||
cat = CategoryFactory.build()
|
||||
budget = CategoryBudget(
|
||||
category=cat,
|
||||
amount_eur=100.00,
|
||||
amount_eur=Decimal("100.00"),
|
||||
valid_from=date(2026, 10, 1),
|
||||
valid_to=date(2026, 10, 1),
|
||||
)
|
||||
@@ -133,7 +144,7 @@ class TestCategoryBudgetValidation:
|
||||
cat = CategoryFactory.build()
|
||||
budget = CategoryBudget(
|
||||
category=cat,
|
||||
amount_eur=150.75,
|
||||
amount_eur=Decimal("150.75"),
|
||||
valid_from=date(2026, 9, 1),
|
||||
valid_to=date(2026, 9, 30),
|
||||
)
|
||||
@@ -142,7 +153,7 @@ class TestCategoryBudgetValidation:
|
||||
try:
|
||||
CategoryBudget(
|
||||
category=cat,
|
||||
amount_eur=200.00,
|
||||
amount_eur=Decimal("200.00"),
|
||||
valid_from=date(2026, 12, 31),
|
||||
valid_to=date(2026, 12, 30),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
from src.models.category import Category
|
||||
from src.models.category_tree import CategoryTree, _transaction_to_category_branch
|
||||
from src.models.transactions import Transaction
|
||||
from tests.factories import TransactionFactory
|
||||
|
||||
|
||||
def test_transaction_to_category_branch_simple():
|
||||
transaction = TransactionFactory.build(category=Category.from_str("dummy category"))
|
||||
result = _transaction_to_category_branch(transaction)
|
||||
assert isinstance(result, CategoryTree)
|
||||
assert result.category
|
||||
assert result.category.name == "dummy category"
|
||||
assert result.transactions
|
||||
assert isinstance(result.transactions, list)
|
||||
assert len(result.transactions) == 1
|
||||
assert isinstance(result.transactions[0], Transaction)
|
||||
assert result.transactions[0] == transaction
|
||||
|
||||
|
||||
def test_transaction_to_category_branch_two_category_levels():
|
||||
transaction = TransactionFactory.build(category=Category.from_str("father:son"))
|
||||
result = _transaction_to_category_branch(transaction)
|
||||
assert isinstance(result, CategoryTree)
|
||||
assert result.category
|
||||
assert result.category.name == "father"
|
||||
assert result.transactions is None
|
||||
assert result.sub_categories
|
||||
assert isinstance(result.sub_categories, list)
|
||||
assert len(result.sub_categories) == 1
|
||||
assert isinstance(result.sub_categories[0], CategoryTree)
|
||||
assert isinstance(result.sub_categories[0].category, Category)
|
||||
assert result.sub_categories[0].category.name == "son"
|
||||
assert result.sub_categories[0].transactions
|
||||
assert isinstance(result.sub_categories[0].transactions, list)
|
||||
assert len(result.sub_categories[0].transactions) == 1
|
||||
assert isinstance(result.sub_categories[0].transactions[0], Transaction)
|
||||
assert result.sub_categories[0].transactions[0] == transaction
|
||||
|
||||
|
||||
def test_transaction_to_category_branch_no_category():
|
||||
transaction = TransactionFactory.build(category=None)
|
||||
result = _transaction_to_category_branch(transaction)
|
||||
assert result.category is None
|
||||
assert result.transactions
|
||||
assert isinstance(result.transactions, list)
|
||||
assert len(result.transactions) == 1
|
||||
assert result.transactions[0] == transaction
|
||||
@@ -101,7 +101,7 @@ def test_map_comdirect_giro_to_transaction():
|
||||
assert transaction.amount == Decimal("32.43")
|
||||
assert transaction.other_name is None
|
||||
assert transaction.other_iban is None
|
||||
assert transaction.category_id is None
|
||||
assert transaction.category is None
|
||||
|
||||
|
||||
def test_consolidate_b4w_signed_amount_and_category(db_session: Session):
|
||||
@@ -125,6 +125,9 @@ def test_consolidate_b4w_signed_amount_and_category(db_session: Session):
|
||||
assert transaction.amount == Decimal("-50.00")
|
||||
assert transaction.category_id == staging.category_id
|
||||
assert transaction.text == "Supermarket"
|
||||
mapped = map_b4w_to_transaction(staging)
|
||||
assert mapped.category is not None
|
||||
assert mapped.category.to_str() == export.category.to_str()
|
||||
|
||||
|
||||
def test_ingest_b4w_e2e_idempotent(db_session: Session):
|
||||
|
||||
Reference in New Issue
Block a user