"""Test validation of approval metadata""" # 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 pytest from . import testutil from conservancy_beancount.plugin import meta_approval REQUIRED_ACCOUNTS = { 'Assets:Bank:Checking', 'Assets:Cash', 'Assets:Savings', } NON_REQUIRED_ACCOUNTS = { 'Assets:Prepaid:Expenses', # Receivables are checked by meta_receivable_documentation 'Assets:Receivable:Accounts', 'Equity:QpeningBalance', 'Expenses:Other', 'Income:Other', # Payables are checked by meta_payable_documentation 'Liabilities:Payable:Accounts', 'Liabilities:Payable:Vacation', 'Liabilities:UnearnedIncome:Donations', } CREDITCARD_ACCOUNT = 'Liabilities:CreditCard' TEST_KEY = 'approval' @pytest.fixture(scope='module') def hook(): config = testutil.TestConfig() return meta_approval.MetaApproval(config) @pytest.mark.parametrize('acct1,acct2,value', testutil.combine_values( REQUIRED_ACCOUNTS, NON_REQUIRED_ACCOUNTS, testutil.LINK_METADATA_STRINGS, )) def test_valid_values_on_postings(hook, acct1, acct2, value): txn = testutil.Transaction(postings=[ (acct2, 25), (acct1, -25, {TEST_KEY: value}), ]) assert not list(hook.run(txn)) @pytest.mark.parametrize('acct1,acct2,value', testutil.combine_values( REQUIRED_ACCOUNTS, NON_REQUIRED_ACCOUNTS, testutil.NON_LINK_METADATA_STRINGS, )) def test_invalid_values_on_postings(hook, acct1, acct2, value): txn = testutil.Transaction(postings=[ (acct2, 25), (acct1, -25, {TEST_KEY: value}), ]) actual = {error.message for error in hook.run(txn)} assert actual == {"{} missing {}".format(acct1, TEST_KEY)} @pytest.mark.parametrize('acct1,acct2,value', testutil.combine_values( REQUIRED_ACCOUNTS, NON_REQUIRED_ACCOUNTS, testutil.NON_STRING_METADATA_VALUES, )) def test_bad_type_values_on_postings(hook, acct1, acct2, value): txn = testutil.Transaction(postings=[ (acct2, 25), (acct1, -25, {TEST_KEY: value}), ]) expected_msg = "{} has wrong type of {}: expected str but is a {}".format( acct1, TEST_KEY, type(value).__name__, ) actual = {error.message for error in hook.run(txn)} assert expected_msg in actual @pytest.mark.parametrize('acct1,acct2,value', testutil.combine_values( REQUIRED_ACCOUNTS, NON_REQUIRED_ACCOUNTS, testutil.LINK_METADATA_STRINGS, )) def test_valid_values_on_transaction(hook, acct1, acct2, value): txn = testutil.Transaction(**{TEST_KEY: value}, postings=[ (acct2, 25), (acct1, -25), ]) assert not list(hook.run(txn)) @pytest.mark.parametrize('acct1,acct2,value', testutil.combine_values( REQUIRED_ACCOUNTS, NON_REQUIRED_ACCOUNTS, testutil.NON_LINK_METADATA_STRINGS, )) def test_invalid_values_on_transaction(hook, acct1, acct2, value): txn = testutil.Transaction(**{TEST_KEY: value}, postings=[ (acct2, 25), (acct1, -25), ]) actual = {error.message for error in hook.run(txn)} assert actual == {"{} missing {}".format(acct1, TEST_KEY)} @pytest.mark.parametrize('acct1,acct2,value', testutil.combine_values( REQUIRED_ACCOUNTS, NON_REQUIRED_ACCOUNTS, testutil.NON_STRING_METADATA_VALUES, )) def test_bad_type_values_on_transaction(hook, acct1, acct2, value): txn = testutil.Transaction(**{TEST_KEY: value}, postings=[ (acct2, 25), (acct1, -25), ]) expected_msg = "{} has wrong type of {}: expected str but is a {}".format( acct1, TEST_KEY, type(value).__name__, ) actual = {error.message for error in hook.run(txn)} assert expected_msg in actual @pytest.mark.parametrize('acct1,acct2', testutil.combine_values( REQUIRED_ACCOUNTS, NON_REQUIRED_ACCOUNTS, )) def test_approval_not_required_on_credits(hook, acct1, acct2): txn = testutil.Transaction(postings=[ (acct2, -25), (acct1, 25), ]) assert not list(hook.run(txn)) def test_approval_not_required_to_charge_credit_card(hook): txn = testutil.Transaction(postings=[ ('Expenses:Other', 25), (CREDITCARD_ACCOUNT, 25), ]) assert not list(hook.run(txn)) def test_approval_not_required_to_pay_credit_card(hook): txn = testutil.Transaction(postings=[ ('Assets:Checking', -25), (CREDITCARD_ACCOUNT, 25), ]) assert not list(hook.run(txn)) @pytest.mark.parametrize('tax_implication,other_acct', [ ('Bank-Transfer', 'Assets:Savings'), ('Chargeback', 'Income:Donations'), ]) def test_approval_not_required_by_tax_implication(hook, tax_implication, other_acct): txn = testutil.Transaction(postings=[ ('Assets:Checking', -250, {'tax-implication': tax_implication}), (other_acct, 245), ('Expenses:BankingFees', 5), ]) assert not list(hook.run(txn)) def test_not_required_on_flagged(hook): txn = testutil.Transaction(flag='!', postings=[ ('Assets:Checking', -25), ('Liabilities:Payable:Accounts', 25), ]) assert not list(hook.run(txn))