"""Test handling of project 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. from pathlib import Path import pytest from . import testutil from conservancy_beancount import errors as errormod from conservancy_beancount.plugin import meta_project VALID_VALUES = { 'Conservancy': 'Conservancy', 'Alpha': 'Alpha', 'Bravo': 'Bravo', 'Charles': 'Charlie', 'Chuck': 'Charlie', } INVALID_VALUES = { 'Alhpa', 'Yankee', '', } TEST_KEY = 'project' DEFAULT_VALUE = 'Conservancy' @pytest.fixture(scope='module') def hook(): config = testutil.TestConfig(repo_path='repository') return meta_project.MetaProject(config) @pytest.mark.parametrize('src_value,set_value', VALID_VALUES.items()) def test_valid_values_on_postings(hook, src_value, set_value): txn = testutil.Transaction(postings=[ ('Assets:Cash', -25), ('Expenses:General', 25, {TEST_KEY: src_value}), ]) errors = list(hook.run(txn)) assert not errors testutil.check_post_meta(txn, None, {TEST_KEY: set_value}) @pytest.mark.parametrize('src_value', INVALID_VALUES) def test_invalid_values_on_postings(hook, src_value): txn = testutil.Transaction(postings=[ ('Assets:Cash', -25), ('Expenses:General', 25, {TEST_KEY: src_value}), ]) errors = list(hook.run(txn)) assert errors testutil.check_post_meta(txn, None, {TEST_KEY: src_value}) @pytest.mark.parametrize('src_value,set_value', VALID_VALUES.items()) def test_valid_values_on_transactions(hook, src_value, set_value): txn = testutil.Transaction(**{TEST_KEY: src_value}, postings=[ ('Assets:Cash', -25), ('Expenses:General', 25), ]) errors = list(hook.run(txn)) assert not errors testutil.check_post_meta(txn, None, {TEST_KEY: set_value}) @pytest.mark.parametrize('src_value', INVALID_VALUES) def test_invalid_values_on_transactions(hook, src_value): txn = testutil.Transaction(**{TEST_KEY: src_value}, postings=[ ('Assets:Cash', -25), ('Expenses:General', 25), ]) errors = list(hook.run(txn)) assert errors testutil.check_post_meta(txn, None, None) @pytest.mark.parametrize('account,required', [ ('Assets:Cash', False), ('Assets:Receivable:Accounts', True), ('Assets:Receivable:Loans', True), ('Equity:OpeningBalance', False), ('Equity:Funds:Restricted', True), ('Equity:Funds:Unrestricted', False), ('Expenses:General', True), ('Income:Donations', True), ('Liabilities:CreditCard', False), ('Liabilities:Payable:Accounts', True), # We do want a "project" for Lia:Pay:Vacation but it has a default value ('Liabilities:Payable:Vacation', False), ('Liabilities:UnearnedIncome:Donations', True), ]) def test_which_accounts_required_on(hook, account, required): txn = testutil.Transaction(postings=[ ('Assets:Checking', 25), (account, 25), ]) errors = list(hook.run(txn)) assert required == any(errors) @pytest.mark.parametrize('account', [ 'Equity:Funds:Unrestricted', 'Equity:Realized:CurrencyConversion', 'Expenses:Payroll:Salary', 'Expenses:Payroll:Tax', 'Liabilities:Payable:Vacation', ]) def test_default_values(hook, account): txn = testutil.Transaction(postings=[ ('Assets:Checking', -25), (account, 25), ]) errors = list(hook.run(txn)) assert not errors testutil.check_post_meta(txn, None, {TEST_KEY: DEFAULT_VALUE}) @pytest.mark.parametrize('equity,other_acct,value', testutil.combine_values( ['Equity:Funds:Unrestricted', 'Equity:Realized:CurrencyConversion'], ['Assets:Checking', 'Liabilities:CreditCard'], VALID_VALUES, )) def test_equity_override_txn_meta(hook, equity, other_acct, value): if value == DEFAULT_VALUE: value = f'Not{value}' txn = testutil.Transaction(**{TEST_KEY: value}, postings=[ (other_acct, 100), (equity, -100), ]) errors = list(hook.run(txn)) assert not errors testutil.check_post_meta(txn, None, {TEST_KEY: DEFAULT_VALUE}) @pytest.mark.parametrize('equity,other_acct,value', testutil.combine_values( ['Equity:Funds:Unrestricted', 'Equity:Realized:CurrencyConversion'], ['Assets:Checking', 'Liabilities:CreditCard'], VALID_VALUES, )) def test_equity_override_post_meta(hook, equity, other_acct, value): if value == DEFAULT_VALUE: value = f'Not{value}' txn = testutil.Transaction(postings=[ (other_acct, 100), (equity, -100, {TEST_KEY: value}), ]) actual = {error.message for error in hook.run(txn)} assert actual == {f"{equity} has invalid {TEST_KEY}: {value}"} testutil.check_post_meta(txn, None, {TEST_KEY: DEFAULT_VALUE}) @pytest.mark.parametrize('date,required', [ (testutil.EXTREME_FUTURE_DATE, False), (testutil.FUTURE_DATE, True), (testutil.FY_START_DATE, True), (testutil.FY_MID_DATE, True), (testutil.PAST_DATE, None), ]) def test_default_value_set_in_date_range(hook, date, required): txn = testutil.Transaction(date=date, postings=[ ('Expenses:Payroll:Benefits', 25), ('Liabilities:Payable:Vacation', -25), ]) errors = list(hook.run(txn)) assert not errors expect_meta = {TEST_KEY: DEFAULT_VALUE} if required else None testutil.check_post_meta(txn, expect_meta, expect_meta) @pytest.mark.parametrize('repo_path', [ None, '..', ]) def test_missing_project_data(repo_path): config = testutil.TestConfig(repo_path=repo_path) with pytest.raises(errormod.ConfigurationError): meta_project.MetaProject(config) @pytest.mark.parametrize('repo_path_s,data_path_s', [ ('repository', 'Projects/project-list.yml'), ('..', 'LICENSE.txt'), ]) def test_invalid_project_data(repo_path_s, data_path_s): config = testutil.TestConfig(repo_path=repo_path_s) with pytest.raises(errormod.ConfigurationError): meta_project.MetaProject(config, Path(data_path_s)) def test_not_required_on_opening(hook): txn = testutil.OpeningBalance('Equity:Funds:Unrestricted') assert not list(hook.run(txn)) def test_always_required_on_restricted_funds(hook): acct = 'Equity:Funds:Restricted' txn = testutil.OpeningBalance(acct) actual = {error.message for error in hook.run(txn)} assert actual == {f'{acct} missing project'} @pytest.mark.parametrize('src_value', INVALID_VALUES) def test_still_required_on_flagged_txn(hook, src_value): txn = testutil.Transaction(flag='!', **{TEST_KEY: src_value}, postings=[ ('Assets:Cash', -25), ('Expenses:General', 25), ]) errors = list(hook.run(txn)) assert errors testutil.check_post_meta(txn, None, None) @pytest.mark.parametrize('src_value', testutil.FIXME_VALUES) def test_flagged_fixme_ok(hook, src_value): txn = testutil.Transaction(flag='!', postings=[ ('Assets:Cash', -25), ('Expenses:General', 25, {TEST_KEY: src_value}), ]) errors = list(hook.run(txn)) assert not errors testutil.check_post_meta(txn, None, {TEST_KEY: src_value})