Files @ 9be7fdd95f73
Branch filter:

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

bsturmfels
reconcile.helper: Avoid rt >= 3.0 library due to breaking changes

Error relates to rt.Rt not existing.
"""Test main plugin"""
# 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 datetime

from decimal import Decimal

import pytest

from . import testutil

import beancount.core.data as bc_data

from conservancy_beancount import beancount_types, errors as errormod, plugin

HOOK_REGISTRY = plugin.HookRegistry()

class NonError(errormod.Error):
    pass


class TransactionHook:
    DIRECTIVE = beancount_types.Transaction
    HOOK_GROUPS = frozenset()

    def __init__(self, config):
        self.config = config

    def run(self, txn):
        assert False, "something called base class run method"


@HOOK_REGISTRY.add_hook
class ConfigurationError(TransactionHook):
    HOOK_GROUPS = frozenset(['unconfigured'])

    def __init__(self, config):
        raise errormod.ConfigurationError("testing error")


@HOOK_REGISTRY.add_hook
class TransactionError(TransactionHook):
    HOOK_GROUPS = frozenset(['configured'])

    def run(self, txn):
        return [NonError('txn:{}'.format(id(txn)), txn)]


@HOOK_REGISTRY.add_hook
class PostingError(TransactionHook):
    HOOK_GROUPS = frozenset(['configured', 'posting'])

    def run(self, txn):
        return [NonError('post:{}'.format(id(post)), txn)
                for post in txn.postings]


@pytest.fixture
def config_map():
    return {}

@pytest.fixture
def easy_entries():
    return [
        testutil.Transaction(postings=[
            ('Income:Donations', -25),
            ('Assets:Cash', 25),
        ]),
        testutil.Transaction(postings=[
            ('Expenses:General', 10),
            ('Liabilites:CreditCard', -10),
        ]),
    ]

def map_errors(errors):
    retval = {}
    for error in errors:
        key, _, errid = error.message.partition(':')
        retval.setdefault(key, set()).add(errid)
    return retval

@pytest.mark.parametrize('group_str,expected', [
    (None, [TransactionError, PostingError]),
    ('', [TransactionError, PostingError]),
    ('all', [TransactionError, PostingError]),
    ('Transaction', [TransactionError, PostingError]),
    ('-posting', [TransactionError]),
    ('-configured posting', [PostingError]),
    ('configured -posting', [TransactionError]),
])
def test_registry_group_by_directive(group_str, expected):
    args = () if group_str is None else (group_str,)
    actual = {hook for _, hook in HOOK_REGISTRY.group_by_directive(*args)}
    assert actual.issuperset(expected)
    if len(expected) == 1:
        assert not (TransactionError in actual and PostingError in actual)

def test_registry_unknown_group_name():
    with pytest.raises(ValueError):
        next(HOOK_REGISTRY.group_by_directive('UnKnownTestGroup'))

def test_registry_load_included_hooks():
    registry = plugin.HookRegistry()
    assert not list(registry.group_by_directive())
    registry.load_included_hooks()
    actual = {hook.__name__ for key, hook in registry.group_by_directive() if key == 'Transaction'}
    assert len(actual) >= 5
    assert 'MetaProject' in actual
    assert 'MetaRTLinks' in actual

def test_run_with_multiple_hooks(easy_entries, config_map):
    out_entries, errors = plugin.run(easy_entries, config_map, '', HOOK_REGISTRY)
    assert len(out_entries) == 2
    errmap = map_errors(errors)
    assert len(errmap.get('txn', '')) == 2
    assert len(errmap.get('post', '')) == 4

def test_run_with_one_hook(easy_entries, config_map):
    out_entries, errors = plugin.run(easy_entries, config_map, 'posting', HOOK_REGISTRY)
    assert len(out_entries) == 2
    errmap = map_errors(errors)
    assert len(errmap.get('txn', '')) == 0
    assert len(errmap.get('post', '')) == 4

def test_run_on_all_directives(config_map):
    meta = {
        'filename': __file__,
        'lineno': 125,
    }
    date = datetime.date(2020, 3, 1)
    acct = 'Assets:Cash'
    usd = 'USD'
    entries = [
        bc_data.Open(meta, date, acct, [usd], None),
        bc_data.Close(meta, date.replace(year=date.year + 1), acct),
        bc_data.Commodity(meta, date, usd),
        bc_data.Pad(meta, date, acct, 'Income:Other'),
        bc_data.Balance(meta, date, acct, 0, None, None),
        bc_data.Transaction(meta, date, None, None, 'found cash', {}, {}, []),
        bc_data.Note(meta, date, acct, 'test note'),
        bc_data.Event(meta, date, 'test event', 'Test Event 1'),
        bc_data.Query(meta, date, 'test query', ''),
        bc_data.Price(meta, date, 'EUR', (Decimal('1.10508'), usd)),
        bc_data.Document(meta, date, acct, '/TestDocument.txt', None, None),
        bc_data.Custom(meta, date, 'test custom', ['test value']),
    ]
    out_entries, errors = plugin.run(entries, config_map, '-all', HOOK_REGISTRY)
    assert out_entries is entries
    assert not errors