Files @ 5784068904e8
Branch filter:

Location: NPO-Accounting/conservancy_beancount/tests/test_meta_tax_reporting.py

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.
"""test_meta_tax_reporting.py - Unit tests for tax-reporting metadata validation"""
# Copyright © 2021  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_tax_reporting

TEST_KEY = 'tax-reporting'
IMPLICATION_KEY = 'tax-implication'

REQUIRED_ACCOUNTS = {
    'Assets:Checking',
    'Assets:Bank:Savings',
}

NON_REQUIRED_ACCOUNTS = {
    'Assets:Prepaid:Expenses',
    'Assets:Receivable:Accounts',
    'Liabilities:CreditCard',
}

REQUIRED_AMOUNTS = {-50, -500}
NON_REQUIRED_AMOUNTS = {-5, 500}

REQUIRED_IMPLICATIONS = {
    '1099',
    '1099-Misc-Other',
    'foreign-grantee',
    'Foreign-Individual-Contractor',
    'USA-501c3',
    'US-Grantee',
}

NON_REQUIRED_IMPLICATIONS = {
    'Bank-Transfer',
    'chargeback',
    'Foreign-Corp',
    'Loan',
    'refund',
    'Reimbursement',
    'retirement-pretax',
    'Tax-Payment',
    'us-corp',
    'w2',
}

@pytest.fixture(scope='module')
def hook():
    config = testutil.TestConfig(payment_threshold=10)
    return meta_tax_reporting.MetaTaxReporting(config)

@pytest.mark.parametrize('account,amount,implication,value', testutil.combine_values(
    REQUIRED_ACCOUNTS,
    REQUIRED_AMOUNTS,
    REQUIRED_IMPLICATIONS,
    testutil.LINK_METADATA_STRINGS,
))
def test_pass_on_txn(hook, account, amount, implication, value):
    txn_meta = {
        IMPLICATION_KEY: implication,
        TEST_KEY: value,
    }
    txn = testutil.Transaction(**txn_meta, postings=[
        (account, amount),
        ('Expenses:Other', -amount),
    ])
    assert not list(hook.run(txn))

@pytest.mark.parametrize('account,amount,implication,value', testutil.combine_values(
    REQUIRED_ACCOUNTS,
    REQUIRED_AMOUNTS,
    REQUIRED_IMPLICATIONS,
    testutil.LINK_METADATA_STRINGS,
))
def test_pass_on_post(hook, account, amount, implication, value):
    post_meta = {
        IMPLICATION_KEY: implication,
        TEST_KEY: value,
    }
    txn = testutil.Transaction(postings=[
        (account, amount, post_meta),
        ('Expenses:Other', -amount),
    ])
    assert not list(hook.run(txn))

@pytest.mark.parametrize('account,amount,implication', testutil.combine_values(
    REQUIRED_ACCOUNTS,
    REQUIRED_AMOUNTS,
    REQUIRED_IMPLICATIONS,
))
def test_error_when_missing(hook, account, amount, implication):
    txn = testutil.Transaction(postings=[
        (account, amount, {IMPLICATION_KEY: implication}),
        ('Expenses:Other', -amount),
    ])
    assert list(hook.run(txn))

@pytest.mark.parametrize('account,amount,implication,value', testutil.combine_values(
    REQUIRED_ACCOUNTS,
    REQUIRED_AMOUNTS,
    REQUIRED_IMPLICATIONS,
    testutil.NON_LINK_METADATA_STRINGS,
))
def test_error_when_empty(hook, account, amount, implication, value):
    txn_meta = {
        IMPLICATION_KEY: implication,
        TEST_KEY: value,
    }
    txn = testutil.Transaction(**txn_meta, postings=[
        (account, amount),
        ('Expenses:Other', -amount),
    ])
    assert list(hook.run(txn))

@pytest.mark.parametrize('account,amount,implication,value', testutil.combine_values(
    REQUIRED_ACCOUNTS,
    REQUIRED_AMOUNTS,
    REQUIRED_IMPLICATIONS,
    testutil.NON_STRING_METADATA_VALUES,
))
def test_error_when_wrong_type(hook, account, amount, implication, value):
    txn_meta = {
        IMPLICATION_KEY: implication,
        TEST_KEY: value,
    }
    txn = testutil.Transaction(**txn_meta, postings=[
        (account, amount),
        ('Expenses:Other', -amount),
    ])
    assert list(hook.run(txn))

@pytest.mark.parametrize('account,amount,implication', testutil.combine_values(
    NON_REQUIRED_ACCOUNTS,
    REQUIRED_AMOUNTS,
    REQUIRED_IMPLICATIONS,
))
def test_skip_by_account(hook, account, amount, implication):
    txn = testutil.Transaction(postings=[
        (account, amount, {IMPLICATION_KEY: implication}),
        ('Expenses:Other', -amount),
    ])
    assert not list(hook.run(txn))

@pytest.mark.parametrize('account,amount,implication', testutil.combine_values(
    REQUIRED_ACCOUNTS,
    NON_REQUIRED_AMOUNTS,
    REQUIRED_IMPLICATIONS,
))
def test_skip_by_amount(hook, account, amount, implication):
    txn = testutil.Transaction(postings=[
        (account, amount, {IMPLICATION_KEY: implication}),
        ('Expenses:Other', -amount),
    ])
    assert not list(hook.run(txn))

@pytest.mark.parametrize('account,amount,implication', testutil.combine_values(
    REQUIRED_ACCOUNTS,
    REQUIRED_AMOUNTS,
    NON_REQUIRED_IMPLICATIONS,
))
def test_skip_by_implication(hook, account, amount, implication):
    txn = testutil.Transaction(postings=[
        (account, amount, {IMPLICATION_KEY: implication}),
        ('Expenses:Other', -amount),
    ])
    assert not list(hook.run(txn))

@pytest.mark.parametrize('account,amount,implication', testutil.combine_values(
    REQUIRED_ACCOUNTS,
    REQUIRED_AMOUNTS,
    REQUIRED_IMPLICATIONS,
))
def test_skip_by_flag(hook, account, amount, implication):
    txn = testutil.Transaction(flag='!', postings=[
        (account, amount, {IMPLICATION_KEY: implication}),
        ('Expenses:Other', -amount),
    ])
    assert not list(hook.run(txn))