Files @ 5784068904e8
Branch filter:

Location: NPO-Accounting/conservancy_beancount/tests/test_data_posting_meta.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 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'