Files @ 5784068904e8
Branch filter:

Location: NPO-Accounting/conservancy_beancount/tests/test_meta_repo_links.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 link checker for repository documents"""
# 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 itertools

from pathlib import Path

import pytest

from . import testutil

from conservancy_beancount import errors as errormod
from conservancy_beancount.plugin import meta_repo_links

METADATA_KEYS = [
    'approval',
    'bank-statement',
    'check',
    'contract',
    'invoice',
    'purchase-order',
    'receipt',
    'statement',
    'tax-statement',
]

GOOD_LINKS = [Path(s) for s in [
    'Projects/project-data.yml',
    'Projects/project-list.yml',
]]

BAD_LINKS = [Path(s) for s in [
    'NonexistentDirectory/NonexistentFile1.txt',
    'NonexistentDirectory/NonexistentFile2.txt',
    'egproto:',
    'egproto:123',
    'egproto:123/456',
    'egproto:foo'
    'egproto:/foo/bar',
    ';egproto::',
]]

NOT_FOUND_MSG = '{} not found in repository: {}'.format

def build_meta(keys=None, *sources):
    if keys is None:
        keys = iter(METADATA_KEYS)
    sources = (itertools.cycle(src) for src in sources)
    return {key: ' '.join(str(x) for x in rest)
            for key, *rest in zip(keys, *sources)}

@pytest.fixture(scope='module')
def hook():
    config = testutil.TestConfig(repo_path='repository')
    return meta_repo_links.MetaRepoLinks(config)

def test_error_with_no_repository():
    config = testutil.TestConfig(repo_path=None)
    with pytest.raises(errormod.ConfigurationError):
        meta_repo_links.MetaRepoLinks(config)

def test_good_txn_links(hook):
    meta = build_meta(None, GOOD_LINKS)
    txn = testutil.Transaction(**meta, postings=[
        ('Income:Donations', -5),
        ('Assets:Cash', 5),
    ])
    assert not list(hook.run(txn))

def test_good_post_links(hook):
    meta = build_meta(None, GOOD_LINKS)
    txn = testutil.Transaction(postings=[
        ('Income:Donations', -5, meta),
        ('Assets:Cash', 5),
    ])
    assert not list(hook.run(txn))

def test_bad_txn_links(hook):
    meta = build_meta(None, BAD_LINKS)
    txn = testutil.Transaction(**meta, postings=[
        ('Income:Donations', -5),
        ('Assets:Cash', 5),
    ])
    expected = {NOT_FOUND_MSG(key, value) for key, value in meta.items()}
    actual = {error.message for error in hook.run(txn)}
    assert expected == actual

def test_bad_post_links(hook):
    meta = build_meta(None, BAD_LINKS)
    txn = testutil.Transaction(postings=[
        ('Income:Donations', -5, meta.copy()),
        ('Assets:Cash', 5),
    ])
    expected = {NOT_FOUND_MSG(key, value) for key, value in meta.items()}
    actual = {error.message for error in hook.run(txn)}
    assert expected == actual

def test_flagged_txn_not_checked(hook):
    keys = iter(METADATA_KEYS)
    txn_meta = build_meta(keys, BAD_LINKS)
    txn_meta['flag'] = '!'
    txn = testutil.Transaction(**txn_meta, postings=[
        ('Income:Donations', -5, build_meta(keys, BAD_LINKS)),
        ('Assets:Checking', 5, build_meta(keys, BAD_LINKS)),
    ])
    assert not list(hook.run(txn))

@pytest.mark.parametrize('value', testutil.NON_STRING_METADATA_VALUES)
def test_bad_metadata_type(hook, value):
    txn = testutil.Transaction(**{'check': value}, postings=[
        ('Income:Donations', -5),
        ('Assets:Cash', 5),
    ])
    expected = {'transaction has wrong type of check: expected str but is a {}'.format(
        type(value).__name__,
    )}
    actual = {error.message for error in hook.run(txn)}
    assert expected == actual

@pytest.mark.parametrize('ext_doc', [
    'rt:123',
    'rt:456/789',
    'rt://ticket/23',
    'rt://ticket/34/attachments/567890',
])
def test_docs_outside_repository_not_checked(hook, ext_doc):
    txn = testutil.Transaction(
        receipt='{} {} {}'.format(GOOD_LINKS[0], ext_doc, BAD_LINKS[1]),
        postings=[
            ('Income:Donations', -5),
            ('Assets:Cash', 5),
        ])
    expected = {NOT_FOUND_MSG('receipt', BAD_LINKS[1])}
    actual = {error.message for error in hook.run(txn)}
    assert expected == actual

def test_mixed_results(hook):
    txn = testutil.Transaction(
        approval='{} {}'.format(*GOOD_LINKS),
        contract='{} {}'.format(BAD_LINKS[0], GOOD_LINKS[1]),
        postings=[
            ('Income:Donations', -5, {'invoice': '{} {}'.format(*BAD_LINKS)}),
            ('Assets:Cash', 5, {'statement': '{} {}'.format(GOOD_LINKS[0], BAD_LINKS[1])}),
        ])
    expected = {
        NOT_FOUND_MSG('contract', BAD_LINKS[0]),
        NOT_FOUND_MSG('invoice', BAD_LINKS[0]),
        NOT_FOUND_MSG('invoice', BAD_LINKS[1]),
        NOT_FOUND_MSG('statement', BAD_LINKS[1]),
    }
    actual = {error.message for error in hook.run(txn)}
    assert expected == actual