import2ledger/tests/test_templates.py
Brett Smith 76f2707aac hooks.ledger_entry: New hook to output Ledger entries.
This is roughly the smallest diff necessary to move output to a hook.
There's a lot of code reorganization that should still happen to bring it
better in line with this new structure.
2017-12-31 12:35:20 -05:00

249 lines
7.1 KiB
Python

import collections
import configparser
import contextlib
import datetime
import decimal
import io
import pathlib
import pytest
from import2ledger import errors, template
from import2ledger.hooks import ledger_entry
from . import DATA_DIR, normalize_whitespace
DATE = datetime.date(2015, 3, 14)
config = configparser.ConfigParser(comment_prefixes='#')
with pathlib.Path(DATA_DIR, 'templates.ini').open() as conffile:
config.read_file(conffile)
def template_from(section_name, *args, **kwargs):
return template.Template(config[section_name]['template'], *args, **kwargs)
def template_vars(payee, amount, currency='USD', date=DATE, other_vars=None):
call_vars = {
'amount': decimal.Decimal(amount),
'currency': currency,
'date': date,
'payee': payee,
}
if other_vars is None:
return call_vars
else:
return collections.ChainMap(call_vars, other_vars)
def render_lines(render_vars, section_name, *args, **kwargs):
tmpl = template_from(section_name, *args, **kwargs)
rendered = tmpl.render(render_vars)
return [normalize_whitespace(s) for s in rendered.splitlines()]
def assert_easy_render(tmpl, entity, amount, currency, expect_date, expect_amt):
rendered = tmpl.render(template_vars(entity, amount, currency))
lines = [normalize_whitespace(s) for s in rendered.splitlines()]
assert lines == [
"",
"{} {}".format(expect_date, entity),
" Accrued:Accounts Receivable " + expect_amt,
" Income:Donations " + expect_amt.replace(amount, "-" + amount),
]
assert not tmpl.is_empty()
def test_easy_template():
tmpl = template_from('Simplest')
assert_easy_render(tmpl, 'JJ', '5.99', 'CAD', '2015/03/14', '5.99 CAD')
def test_date_formatting():
tmpl = template_from('Simplest', date_fmt='%Y-%m-%d')
assert_easy_render(tmpl, 'KK', '6.99', 'CAD', '2015-03-14', '6.99 CAD')
def test_currency_formatting():
tmpl = template_from('Simplest', signed_currencies=['USD'])
assert_easy_render(tmpl, 'CC', '7.99', 'USD', '2015/03/14', '$7.99')
def test_empty_template():
tmpl = template.Template("\n \n")
assert tmpl.render(template_vars('BB', '8.99')) == ''
assert tmpl.is_empty()
def test_complex_template():
render_vars = template_vars('TT', '125.50', other_vars={
'entity': 'T-T',
'program': 'Spectrum Defense',
'txid': 'ABCDEF',
})
lines = render_lines(
render_vars, 'Complex',
date_fmt='%Y-%m-%d',
signed_currencies=['USD'],
)
assert lines == [
"",
"2015-03-14 TT",
" ;Tag: Value",
" ;TransactionID: ABCDEF",
" Accrued:Accounts Receivable $125.50",
" ;Entity: Supplier",
" Income:Donations:Spectrum Defense $-119.85",
" ;Program: Spectrum Defense",
" ;Entity: T-T",
" Income:Donations:General $-5.65",
" ;Entity: T-T",
]
def test_balancing():
lines = render_lines(template_vars('FF', '1.01'), 'FiftyFifty')
assert lines == [
"",
"2015/03/14 FF",
" Accrued:Accounts Receivable 1.01 USD",
" Income:Donations -0.50 USD",
" Income:Sales -0.51 USD",
]
def test_multivalue():
render_vars = template_vars('DD', '150.00', other_vars={
'tax': decimal.Decimal('12.50'),
})
lines = render_lines(render_vars, 'Multivalue')
assert lines == [
"",
"2015/03/14 DD",
" Expenses:Taxes 12.50 USD",
" ;TaxAuthority: IRS",
" Accrued:Accounts Receivable 137.50 USD",
" Income:RBI -15.00 USD",
" Income:Donations -135.00 USD",
]
def test_zeroed_account_skipped():
render_vars = template_vars('GG', '110.00', other_vars={
'tax': decimal.Decimal(0),
})
lines = render_lines(render_vars, 'Multivalue')
assert lines == [
"",
"2015/03/14 GG",
" Accrued:Accounts Receivable 110.00 USD",
" Income:RBI -11.00 USD",
" Income:Donations -99.00 USD",
]
def test_zeroed_account_last():
render_vars = template_vars('JJ', '90.00', other_vars={
'item_sales': decimal.Decimal(0),
})
lines = render_lines(render_vars, 'Multisplit')
assert lines == [
"",
"2015/03/14 JJ",
" Assets:Cash 90.00 USD",
" Income:Sales -90.00 USD",
" ; :NonItem:",
]
def test_multiple_postings_same_account():
render_vars = template_vars('LL', '80.00', other_vars={
'item_sales': decimal.Decimal(30),
})
lines = render_lines(render_vars, 'Multisplit')
assert lines == [
"",
"2015/03/14 LL",
" Assets:Cash 80.00 USD",
" Income:Sales -50.00 USD",
" ; :NonItem:",
" Income:Sales -30.00 USD",
" ; :Item:",
]
def test_custom_payee_line():
render_vars = template_vars('ZZ', '10.00', other_vars={
'custom_date': datetime.date(2014, 2, 13),
})
lines = render_lines(render_vars, 'Custom Payee')
assert lines == [
"",
"2014/02/13 ZZ - Custom",
" Accrued:Accounts Receivable 10.00 USD",
" Income:Donations -10.00 USD",
]
def test_line1_not_custom_payee():
render_vars = template_vars('VV', '15.00', other_vars={
'custom_date': datetime.date(2014, 2, 12),
})
lines = render_lines(render_vars, 'Simplest')
assert lines == [
"",
"2015/03/14 VV",
" Accrued:Accounts Receivable 15.00 USD",
" Income:Donations -15.00 USD",
]
@pytest.mark.parametrize('amount_expr', [
'',
'name',
'-',
'()',
'+()',
'{}',
'{{}}',
'{()}',
'{name',
'name}',
'{42}',
'(5).real',
'{amount.real}',
'{amount.is_nan()}',
'{Decimal}',
'{FOO}',
])
def test_bad_amount_expression(amount_expr):
with pytest.raises(errors.UserInputError):
template.Template(" Income " + amount_expr)
class Config:
def __init__(self):
self.stdout = io.StringIO()
@contextlib.contextmanager
def open_output_file(self):
yield self.stdout
def get_template(self, key):
try:
return template_from(key)
except KeyError:
raise errors.UserInputConfigurationError(
"template not defined in test config", key)
def run_hook(entry_data):
hook_config = Config()
hook = ledger_entry.LedgerEntryHook(hook_config)
assert hook.run(entry_data) is None
stdout = hook_config.stdout.getvalue()
return normalize_whitespace(stdout).splitlines()
def hook_vars(template_key, payee, amount):
return template_vars(payee, amount, other_vars={'template': template_key})
def test_hook_renders_template():
entry_data = hook_vars('Simplest', 'BB', '0.99')
lines = run_hook(entry_data)
assert lines == [
"",
"2015/03/14 BB",
" Accrued:Accounts Receivable 0.99 USD",
" Income:Donations -0.99 USD",
]
def test_hook_handles_empty_template():
entry_data = hook_vars('Empty', 'CC', 1)
assert not run_hook(entry_data)
def test_hook_handles_template_undefined():
entry_data = hook_vars('Nonexistent', 'DD', 1)
assert not run_hook(entry_data)