Files @ 5784068904e8
Branch filter:

Location: NPO-Accounting/conservancy_beancount/tests/test_data_posting_meta.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.
f4978046b8a2
f4978046b8a2
1b7fdf4f3b00
f4978046b8a2
1b7fdf4f3b00
1b7fdf4f3b00
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
0413fed8b957
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
9b63d898af70
9b63d898af70
9b63d898af70
9b63d898af70
9b63d898af70
9b63d898af70
9b63d898af70
9b63d898af70
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
0413fed8b957
eb7f73e64465
eb7f73e64465
eb7f73e64465
eb7f73e64465
eb7f73e64465
eb7f73e64465
eb7f73e64465
eb7f73e64465
eb7f73e64465
eb7f73e64465
eb7f73e64465
eb7f73e64465
eb7f73e64465
eb7f73e64465
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
52adf1f0a583
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
2b23eba549ad
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
f4978046b8a2
"""Test PostingMeta class"""
# 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 import data

@pytest.fixture
def simple_txn():
    return testutil.Transaction(note='txn note', postings=[
        ('Assets:Cash', 5),
        ('Income:Donations', -5, {'note': 'donation love', 'extra': 'Extra'}),
    ])
SIMPLE_TXN_METAKEYS = frozenset(['filename', 'lineno', 'note'])

@pytest.fixture
def payee_txn():
    return testutil.Transaction(payee='SampleCo', postings=[
        ('Assets:Receivable:Accounts', -100),
        ('Assets:Checking', 95),
        ('Expenses:BankingFees', 5, {'entity': 'MyBank'}),
    ])

def test_getitem_transaction(simple_txn):
    assert data.PostingMeta(simple_txn, 0)['note'] == 'txn note'

def test_getitem_posting(simple_txn):
    assert data.PostingMeta(simple_txn, 1)['note'] == 'donation love'

def test_getitem_keyerror(simple_txn):
    with pytest.raises(KeyError):
        data.PostingMeta(simple_txn, 1)['InvalidMetadata']

def test_setitem_overwrite(simple_txn):
    meta = data.PostingMeta(simple_txn, 1)
    meta['note'] = 'overwritten'
    assert meta['note'] == 'overwritten'
    assert data.PostingMeta(simple_txn, 0)['note'] == 'txn note'

def test_setitem_over_txn(simple_txn):
    meta = data.PostingMeta(simple_txn, 0)
    meta['note'] = 'overwritten'
    assert meta['note'] == 'overwritten'
    assert simple_txn.meta['note'] == 'txn note'
    assert data.PostingMeta(simple_txn, 1)['note'] == 'donation love'

def test_setitem_new_meta(simple_txn):
    meta = data.PostingMeta(simple_txn, 0)
    meta['newkey'] = 'testvalue'
    assert meta['newkey'] == 'testvalue'
    assert 'newkey' not in simple_txn.meta
    assert 'newkey' not in simple_txn.postings[1].meta

def test_delitem(simple_txn):
    meta = data.PostingMeta(simple_txn, 1)
    del meta['note']
    assert 'note' not in simple_txn.postings[1].meta

def test_delitem_fails_on_txn_meta(simple_txn):
    meta = data.PostingMeta(simple_txn, 0)
    with pytest.raises(KeyError):
        del meta['note']

def test_len_with_empty_post_meta(simple_txn):
    assert len(data.PostingMeta(simple_txn, 0)) == len(SIMPLE_TXN_METAKEYS)

def test_len_with_post_meta_over_txn(simple_txn):
    assert len(data.PostingMeta(simple_txn, 1)) == len(SIMPLE_TXN_METAKEYS) + 1

def test_iter_with_empty_post_meta(simple_txn):
    assert set(data.PostingMeta(simple_txn, 0)) == SIMPLE_TXN_METAKEYS

def test_iter_with_post_meta_over_txn(simple_txn):
    assert set(data.PostingMeta(simple_txn, 1)) == SIMPLE_TXN_METAKEYS.union(['extra'])

def test_get_links_from_txn(simple_txn):
    meta = data.PostingMeta(simple_txn, 0)
    assert list(meta.get_links('note')) == ['txn', 'note']

def test_get_links_from_post_override(simple_txn):
    meta = data.PostingMeta(simple_txn, 1)
    assert list(meta.get_links('note')) == ['donation', 'love']

def test_payee_used_as_entity(payee_txn):
    actual = [data.PostingMeta(payee_txn, n, p)['entity']
              for n, p in enumerate(payee_txn.postings)]
    assert actual == ['SampleCo', 'SampleCo', 'MyBank']

def test_entity_metadata_has_precedence_over_payee(payee_txn):
    payee_txn.meta['entity'] = 'ExampleCo'
    actual = [data.PostingMeta(payee_txn, n, p)['entity']
              for n, p in enumerate(payee_txn.postings)]
    assert actual == ['ExampleCo', 'ExampleCo', 'MyBank']

def test_keyerror_when_no_entity_or_payee(simple_txn):
    meta = data.PostingMeta(simple_txn, 1)
    with pytest.raises(KeyError):
        meta['entity']

@pytest.mark.parametrize('date', [
    testutil.FUTURE_DATE,
    testutil.FY_START_DATE,
    testutil.FY_MID_DATE,
    testutil.PAST_DATE,
])
def test_date(date):
    txn = testutil.Transaction(date=date, postings=[
        ('Income:Donations', -15),
        ('Assets:Cash', 15),
    ])
    for index, post in enumerate(txn.postings):
        assert data.PostingMeta(txn, index, post).date == date

def test_mutable_copy():
    txn = testutil.Transaction(
        filename='f', lineno=130, txnkey='one', postings=[
        ('Assets:Cash', 18),
        ('Income:Donations', -18),
    ])
    meta = data.PostingMeta(txn, 1).detached()
    meta['layerkey'] = 'two'
    assert dict(meta) == {
        'filename': 'f',
        'lineno': 130,
        'txnkey': 'one',
        'layerkey': 'two',
    }
    assert 'layerkey' not in txn.meta
    assert all(post.meta is None for post in txn.postings)
    assert meta.date == txn.date

def test_double_detached():
    txn = testutil.Transaction(filename='f', lineno=140, postings=[
        ('Income:Donations', -19),
    ])
    meta1 = data.PostingMeta(txn, 0).detached()
    meta1['metakey'] = 'meta'
    meta1['layerkey'] = 'one'
    meta2 = meta1.detached()
    meta2['layerkey'] = 'two'
    expected = {
        'filename': 'f',
        'lineno': 140,
        'metakey': 'meta',
        'layerkey': 'two',
    }
    assert dict(meta2) == expected
    expected['layerkey'] = 'one'
    assert dict(meta1) == expected
    assert not any(post.meta for post in txn.postings)

# The .get() tests are arguably testing the stdlib, but they're short and
# they confirm that we're using the stdlib as we intend.
def test_get_with_meta_value(simple_txn):
    assert data.PostingMeta(simple_txn, 1).get('note') == 'donation love'

def test_get_with_txn_value(simple_txn):
    assert data.PostingMeta(simple_txn, 0).get('note') == 'txn note'

def test_get_with_no_value(simple_txn):
    assert data.PostingMeta(simple_txn, 0).get('extra') is None

def test_get_with_specified_default(simple_txn):
    assert data.PostingMeta(simple_txn, 0).get('extra', 'blank') == 'blank'