Files @ 5784068904e8
Branch filter:

Location: NPO-Accounting/conservancy_beancount/tests/test_meta_approval.py - annotation

bkuhn
payroll-type — US:403b:Employee:Roth — needed separate since taxable

Since Roth contributions are taxable, there are some reports that
need to include these amounts in total salary (i.e., when running a
report that seeks to show total taxable income for an employee). As
such, we need a `payroll-type` specifically for Roth 403(b)
contributions.
043644d1942f
043644d1942f
1b7fdf4f3b00
043644d1942f
1b7fdf4f3b00
1b7fdf4f3b00
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
c712105bed3c
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
c712105bed3c
212036b25ec5
c712105bed3c
c712105bed3c
043644d1942f
043644d1942f
212036b25ec5
212036b25ec5
bce438167c7c
bce438167c7c
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
043644d1942f
bce438167c7c
043644d1942f
bce438167c7c
043644d1942f
043644d1942f
043644d1942f
f24f941cfbe4
e429a6c6fd91
e429a6c6fd91
e429a6c6fd91
e429a6c6fd91
4789972d3811
f24f941cfbe4
e429a6c6fd91
4789972d3811
4789972d3811
f24f941cfbe4
f24f941cfbe4
f24f941cfbe4
536b50b478d8
536b50b478d8
536b50b478d8
536b50b478d8
536b50b478d8
536b50b478d8
"""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))