diff --git a/tests/test_reports_accrual.py b/tests/test_reports_accrual.py index e880b0d33ffeb4de3b9b5f312c1db7cedf1c0036..8dd22f45fa960c95278136f98740f1c62de71a24 100644 --- a/tests/test_reports_accrual.py +++ b/tests/test_reports_accrual.py @@ -94,8 +94,8 @@ def check_link_regexp(regexp, match_s, first_link_only=False): else: assert end_match -def relate_accruals_by_meta(postings, value, key='invoice'): - return core.RelatedPostings( +def accruals_by_meta(postings, value, key='invoice', wrap_type=iter): + return wrap_type( post for post in postings if post.meta.get(key) == value and post.account.is_under('Assets:Receivable', 'Liabilities:Payable') @@ -200,22 +200,107 @@ def test_report_type_by_unknown_name(arg): with pytest.raises(ValueError): accrual.ReportType.by_name(arg) +@pytest.mark.parametrize('acct_name', ACCOUNTS) +def test_accrual_postings_consistent_account(acct_name): + meta = {'invoice': '{acct_name} invoice.pdf'} + txn = testutil.Transaction(postings=[ + (acct_name, 50, meta), + (acct_name, 25, meta), + ]) + related = accrual.AccrualPostings(data.Posting.from_txn(txn)) + assert related.account == acct_name + assert related.accounts == {acct_name} + +@pytest.mark.parametrize('cost', [ + testutil.Cost('1.2', 'USD'), + None, +]) +def test_accrual_postings_consistent_cost(cost): + meta = {'invoice': 'FXinvoice.pdf'} + txn = testutil.Transaction(postings=[ + (ACCOUNTS[0], 60, 'EUR', cost, meta), + (ACCOUNTS[0], 30, 'EUR', cost, meta), + ]) + related = accrual.AccrualPostings(data.Posting.from_txn(txn)) + assert related.cost == cost + assert related.costs == {cost} + +@pytest.mark.parametrize('meta_key,acct_name', testutil.combine_values( + CONSISTENT_METADATA, + ACCOUNTS, +)) +def test_accrual_postings_consistent_metadata(meta_key, acct_name): + meta_value = f'{meta_key}.pdf' + meta = { + meta_key: meta_value, + 'invoice': f'invoice with {meta_key}.pdf', + } + txn = testutil.Transaction(postings=[ + (acct_name, 70, meta), + (acct_name, 35, meta), + ]) + related = accrual.AccrualPostings(data.Posting.from_txn(txn)) + attr_name = meta_key.replace('-', '_') + assert getattr(related, attr_name) == meta_value + assert getattr(related, f'{attr_name}s') == {meta_value} + +def test_accrual_postings_inconsistent_account(): + meta = {'invoice': 'invoice.pdf'} + txn = testutil.Transaction(postings=[ + (acct_name, index, meta) + for index, acct_name in enumerate(ACCOUNTS) + ]) + related = accrual.AccrualPostings(data.Posting.from_txn(txn)) + assert related.account is related.INCONSISTENT + assert related.accounts == set(ACCOUNTS) + +def test_accrual_postings_inconsistent_cost(): + meta = {'invoice': 'FXinvoice.pdf'} + costs = { + testutil.Cost('1.1', 'USD'), + testutil.Cost('1.2', 'USD'), + } + txn = testutil.Transaction(postings=[ + (ACCOUNTS[0], 10, 'EUR', cost, meta) + for cost in costs + ]) + related = accrual.AccrualPostings(data.Posting.from_txn(txn)) + assert related.cost is related.INCONSISTENT + assert related.costs == costs + +@pytest.mark.parametrize('meta_key,acct_name', testutil.combine_values( + CONSISTENT_METADATA, + ACCOUNTS, +)) +def test_accrual_postings_inconsistent_metadata(meta_key, acct_name): + invoice = 'invoice with {meta_key}.pdf' + meta_value = f'{meta_key}.pdf' + txn = testutil.Transaction(postings=[ + (acct_name, 20, {'invoice': invoice, meta_key: meta_value}), + (acct_name, 35, {'invoice': invoice}), + ]) + related = accrual.AccrualPostings(data.Posting.from_txn(txn)) + attr_name = meta_key.replace('-', '_') + assert getattr(related, attr_name) is related.INCONSISTENT + assert getattr(related, f'{attr_name}s') == {meta_value, None} + @pytest.mark.parametrize('meta_key,account', testutil.combine_values( CONSISTENT_METADATA, ACCOUNTS, )) def test_consistency_check_when_consistent(meta_key, account): invoice = f'test-{meta_key}-invoice' + meta_value = f'test-{meta_key}-value' meta = { 'invoice': invoice, - meta_key: f'test-{meta_key}-value', + meta_key: meta_value, } txn = testutil.Transaction(postings=[ (account, 100, meta), (account, -100, meta), ]) - related = core.RelatedPostings(data.Posting.from_txn(txn)) - assert not list(accrual.consistency_check({invoice: related})) + related = accrual.AccrualPostings(data.Posting.from_txn(txn)) + assert not list(related.report_inconsistencies()) @pytest.mark.parametrize('meta_key,account', testutil.combine_values( ['approval', 'fx-rate', 'statement'], @@ -227,8 +312,8 @@ def test_consistency_check_ignored_metadata(meta_key, account): (account, 100, {'invoice': invoice, meta_key: 'credit'}), (account, -100, {'invoice': invoice, meta_key: 'debit'}), ]) - related = core.RelatedPostings(data.Posting.from_txn(txn)) - assert not list(accrual.consistency_check({invoice: related})) + related = accrual.AccrualPostings(data.Posting.from_txn(txn)) + assert not list(related.report_inconsistencies()) @pytest.mark.parametrize('meta_key,account', testutil.combine_values( CONSISTENT_METADATA, @@ -240,8 +325,8 @@ def test_consistency_check_when_inconsistent(meta_key, account): (account, 100, {'invoice': invoice, meta_key: 'credit', 'lineno': 1}), (account, -100, {'invoice': invoice, meta_key: 'debit', 'lineno': 2}), ]) - related = core.RelatedPostings(data.Posting.from_txn(txn)) - errors = list(accrual.consistency_check({invoice: related})) + related = accrual.AccrualPostings(data.Posting.from_txn(txn)) + errors = list(related.report_inconsistencies()) for exp_lineno, (actual, exp_msg) in enumerate(itertools.zip_longest(errors, [ f'inconsistent {meta_key} for invoice {invoice}: credit', f'inconsistent {meta_key} for invoice {invoice}: debit', @@ -257,8 +342,8 @@ def test_consistency_check_cost(): (account, 100, 'EUR', ('1.1251', 'USD'), {'invoice': invoice, 'lineno': 1}), (account, -100, 'EUR', ('1.125', 'USD'), {'invoice': invoice, 'lineno': 2}), ]) - related = core.RelatedPostings(data.Posting.from_txn(txn)) - errors = list(accrual.consistency_check({invoice: related})) + related = accrual.AccrualPostings(data.Posting.from_txn(txn)) + errors = list(related.report_inconsistencies()) for post, err in itertools.zip_longest(txn.postings, errors): assert err.message == f'inconsistent cost for invoice {invoice}: {post.cost}' assert err.entry is txn @@ -272,7 +357,7 @@ def run_outgoing(invoice, postings, rt_client=None): if rt_client is None: rt_client = RTClient() if not isinstance(postings, core.RelatedPostings): - postings = relate_accruals_by_meta(postings, invoice) + postings = accruals_by_meta(postings, invoice, wrap_type=accrual.AccrualPostings) output = io.StringIO() report = accrual.OutgoingReport(rt_client, output) report.run({invoice: postings}) @@ -285,7 +370,7 @@ def run_outgoing(invoice, postings, rt_client=None): ('rt://ticket/515/attachments/5150', "1,500.00 USD outstanding since 2020-05-15",), ]) def test_balance_report(accrual_postings, invoice, expected, caplog): - related = relate_accruals_by_meta(accrual_postings, invoice) + related = accruals_by_meta(accrual_postings, invoice, wrap_type=accrual.AccrualPostings) output = io.StringIO() report = accrual.BalanceReport(output) report.run({invoice: related})