"""test_filters - Unit tests for filter functions""" # Copyright © 2020 Brett Smith # License: AGPLv3-or-later WITH Beancount-Plugin-Additional-Permission-1.0 # # Full copyright and licensing details can be found at toplevel file # LICENSE.txt in the repository. import itertools import pytest from . import testutil from datetime import date from beancount.core import data as bc_data from conservancy_beancount import data from conservancy_beancount import filters MISSING_POSTING = testutil.Posting('', 0) @pytest.fixture def cc_txn_pair(): dates = testutil.date_seq() txn_meta = { 'payee': 'Smith-Dakota', 'rt-id': 'rt:550', } return [ testutil.Transaction( **txn_meta, date=next(dates), receipt='CCReceipt.pdf', metadate=next(dates), postings=[ ('Liabilities:CreditCard', -36), ('Expenses:Other', 35), ('Expenses:Tax:Sales', 1), ], ), testutil.Transaction( **txn_meta, date=next(dates), receipt='CCPayment.pdf', metadate=next(dates), postings=[ ('Liabilities:CreditCard', 36), ('Assets:Checking', -36, {'statement': 'CheckingStatement.pdf'}), ], ), ] def check_filter(actual, entries, expected_indexes): postings = [post for txn in entries for post in txn.postings] expected = (postings[ii] for ii in expected_indexes) for actual_post, expected_post in itertools.zip_longest( actual, expected, fillvalue=MISSING_POSTING, ): assert actual_post[:-1] == expected_post[:-1] @pytest.mark.parametrize('key,value,expected_indexes', [ ('entity', 'Smith-Dakota', range(5)), ('receipt', 'CCReceipt.pdf', range(3)), ('receipt', 'CCPayment.pdf', range(3, 5)), ('receipt', 'CC', ()), ('statement', 'CheckingStatement.pdf', [4]), ('metadate', date(2020, 9, 2), range(3)), ('metadate', date(2020, 9, 4), range(3, 5)), ('BadKey', '', ()), ('emptykey', '', ()), ]) def test_filter_meta_equal(cc_txn_pair, key, value, expected_indexes): postings = data.Posting.from_entries(cc_txn_pair) actual = filters.filter_meta_equal(postings, key, value) check_filter(actual, cc_txn_pair, expected_indexes) @pytest.mark.parametrize('key,regexp,expected_indexes', [ ('entity', '^Smith-', range(5)), ('receipt', r'\.pdf$', range(5)), ('receipt', 'Receipt', range(3)), ('statement', '.', [4]), ('metadate', 'foo', ()), ('BadKey', '.', ()), ('emptykey', '.', ()), ]) def test_filter_meta_match(cc_txn_pair, key, regexp, expected_indexes): postings = data.Posting.from_entries(cc_txn_pair) actual = filters.filter_meta_match(postings, key, regexp) check_filter(actual, cc_txn_pair, expected_indexes) @pytest.mark.parametrize('ticket_id,expected_indexes', [ (550, range(5)), ('550', range(5)), (55, ()), ('55', ()), (50, ()), ('.', ()), ]) def test_filter_for_rt_id(cc_txn_pair, ticket_id, expected_indexes): postings = data.Posting.from_entries(cc_txn_pair) actual = filters.filter_for_rt_id(postings, ticket_id) check_filter(actual, cc_txn_pair, expected_indexes) @pytest.mark.parametrize('rt_id', [ 'rt:450/', ' rt:450 rt:540', 'rt://ticket/450', 'rt://ticket/450/', ' rt://ticket/450', 'rt://ticket/450 rt://ticket/540', ]) def test_filter_for_rt_id_syntax_variations(rt_id): entries = [testutil.Transaction(**{'rt-id': rt_id}, postings=[ ('Income:Donations', -10), ('Assets:Cash', 10), ])] postings = data.Posting.from_entries(entries) actual = filters.filter_for_rt_id(postings, 450) check_filter(actual, entries, range(2)) def test_filter_for_rt_id_uses_first_link_only(): entries = [testutil.Transaction(postings=[ ('Income:Donations', -10, {'rt-id': 'rt:1 rt:350'}), ('Assets:Cash', 10, {'rt-id': 'rt://ticket/2 rt://ticket/350'}), ])] postings = data.Posting.from_entries(entries) actual = filters.filter_for_rt_id(postings, 350) check_filter(actual, entries, ()), @pytest.mark.parametrize('opening_txn', [ testutil.OpeningBalance(), None, ]) def test_remove_opening_balance_txn(opening_txn): entries = [ testutil.Transaction(postings=[ (account, amount), ('Assets:Checking', -amount), ]) for account, amount in [ ('Income:Donations', -50), ('Expenses:Other', 75), ]] if opening_txn is not None: entries.insert(1, opening_txn) actual = filters.remove_opening_balance_txn(entries) assert actual is opening_txn assert opening_txn not in entries assert not any( post.account.startswith('Equity:') for entry in entries for post in getattr(entry, 'postings', ()) ) @pytest.mark.parametrize('entry', [ bc_data.Custom({}, testutil.FY_START_DATE, 'conservancy_beancount_audit', []), None, ]) def test_audit_date(entry): dates = testutil.date_seq() entries = [ bc_data.Open({}, next(dates), 'Income:Donations', ['USD'], None), bc_data.Open({}, next(dates), 'Assets:Cash', ['USD'], None), testutil.Transaction(postings=[ ('Income:Donations', -10), ('Assets:Cash', 10), ]), ] if entry is not None: entries.append(entry) actual = filters.audit_date(entries) if entry is None: assert actual is None else: assert actual == entry.date def test_iter_unique(): assert list(filters.iter_unique('1213231')) == list('123')