from datetime import date import pytest from finance.models import Account, Category, Payee, Transaction from finance.queries import ( QueryInputError, TransactionFilters, filtered_transactions, ) pytestmark = pytest.mark.django_db def make_account(name="Synthetic Bank", currency="BRL"): return Account.objects.create(name=name, kind=Account.Kind.BANK, currency=currency) def make_transaction(account, *, posting_date, amount="-10.00", **values): return Transaction.objects.create( account=account, amount=amount, posting_date=posting_date, kind=values.pop("kind", Transaction.Kind.EXPENSE), **values, ) def test_filtered_transactions_combines_filters_and_orders_stably(): account = make_account() category = Category.objects.create(name="Synthetic Food", kind=Category.Kind.EXPENSE) older = make_transaction(account, posting_date=date(2026, 1, 5)) newer = make_transaction(account, posting_date=date(2026, 1, 20)) make_transaction(account, posting_date=date(2026, 1, 25), category=category) make_transaction(account, posting_date=date(2026, 2, 1)) make_transaction(make_account("Synthetic USD", "USD"), posting_date=date(2026, 1, 22)) filters = TransactionFilters.from_mapping({ "date_from": "2026-01-01", "date_to": "2026-01-31", "currency": "BRL", "uncategorized": "yes", }) rows = list(filtered_transactions(filters)) assert [row.pk for row in rows] == [newer.pk, older.pk] def test_filtered_transactions_supports_reference_kind_and_categorized_filters(): account = make_account() other_account = make_account("Synthetic Other") category = Category.objects.create(name="Synthetic Food", kind=Category.Kind.EXPENSE) payee = Payee.objects.create(name="Synthetic Merchant") expected = make_transaction( account, posting_date=date(2026, 1, 10), category=category, payee=payee, ) make_transaction(account, posting_date=date(2026, 1, 11), category=category) make_transaction(other_account, posting_date=date(2026, 1, 12), category=category, payee=payee) filters = TransactionFilters.from_mapping({ "account": str(account.pk), "category": str(category.pk), "payee": str(payee.pk), "kind": "EXPENSE", "uncategorized": "no", }) assert list(filtered_transactions(filters)) == [expected] @pytest.mark.parametrize( ("mapping", "errors"), [ ({"date_from": "31/01/2026"}, {"date_from": "invalid_date"}), ({"date_to": "not-a-date"}, {"date_to": "invalid_date"}), ({"account": "bad"}, {"account": "invalid_uuid"}), ({"category": "bad"}, {"category": "invalid_uuid"}), ({"payee": "bad"}, {"payee": "invalid_uuid"}), ({"kind": "UNKNOWN"}, {"kind": "invalid_choice"}), ({"currency": "brl"}, {"currency": "invalid_currency"}), ({"uncategorized": "maybe"}, {"uncategorized": "invalid_choice"}), ], ) def test_invalid_filter_is_explicit(mapping, errors): with pytest.raises(QueryInputError) as caught: TransactionFilters.from_mapping(mapping) assert caught.value.errors == errors def test_filter_errors_are_reported_together_and_date_order_is_checked(): with pytest.raises(QueryInputError) as caught: TransactionFilters.from_mapping({ "date_from": "2026-02-01", "date_to": "2026-01-01", "account": "bad", }) assert caught.value.errors == { "account": "invalid_uuid", "date_to": "before_date_from", }