@@ -6,25 +6,25 @@
# LICENSE.txt in the repository.
import datetime
import pytest
from decimal import Decimal
import yaml
from . import testutil
from conservancy_beancount import data
from conservancy_beancount import data, errors
from conservancy_beancount.reports import rewrite
CMP_OPS = frozenset('< <= == != >= >'.split())
@pytest.mark.parametrize('name', ['Equity:Other', 'Expenses:Other', 'Income:Other'])
@pytest.mark.parametrize('operator', CMP_OPS)
def test_account_condition(name, operator):
operand = 'Expenses:Other'
txn = testutil.Transaction(postings=[(name, -5)])
post, = data.Posting.from_txn(txn)
tester = rewrite.AccountTest(operator, operand)
assert tester(post) == eval(f'name {operator} operand')
@@ -122,25 +122,25 @@ def test_parse_good_condition(subject, operator, operand):
@pytest.mark.parametrize('cond_s', [
'.account = Income:Other', # Bad operator
'.account===Equity:Other', # Bad operand (`=Equity:Other` is not valid)
'.account in foo', # Bad operand
'.date == 1990-90-5', # Bad operand
'.date in 1990-9-9', # Bad operator
'.number > 0xff', # Bad operand
'.number in 16', # Bad operator
'units.number == 5', # Bad subject (syntax)
'.units == 5', # Bad subject (unknown)
])
def test_parse_bad_condition(cond_s):
with pytest.raises(ValueError):
with pytest.raises(errors.RewriteRuleConditionError):
rewrite.TestRegistry.parse(cond_s)
@pytest.mark.parametrize('value', ['Equity:Other', 'Income:Other'])
def test_account_set(value):
value = data.Account(value)
txn = testutil.Transaction(postings=[
('Expenses:Other', 5),
setter = rewrite.AccountSet('=', value)
assert setter(post) == ('account', value)
@@ -186,25 +186,25 @@ def test_parse_good_set(subject, operator, operand):
assert actual.value == operand
@pytest.mark.parametrize('set_s', [
'.account==Equity:Other', # Bad operand (`=Equity:Other` is not valid)
'.account*=2', # Bad operator
'.date = 2020-02-20', # Bad subject
'.number*=0xff', # Bad operand
'.number=5', # Bad operator
'testkey += foo', # Bad operator
'testkey *= 3', # Bad operator
def test_parse_bad_set(set_s):
with pytest.raises(errors.RewriteRuleActionError):
rewrite.SetRegistry.parse(set_s)
def test_good_rewrite_rule():
rule = rewrite.RewriteRule({
'if': ['.account in Income'],
'add': ['income-type = Other'],
})
('Assets:Cash', 10),
('Income:Other', -10),
cash_post, inc_post = data.Posting.from_txn(txn)
@@ -262,44 +262,39 @@ def test_complicated_rewrite_rule():
{'if': ['.date >= 2020-01-02'], 'a': ['.number *= .85'], 'b': ['.number *= .15']},
# Metadata assignment
{'if': ['a==1'], 'then': ['b=2', 'c=3']},
def test_valid_rewrite_rule(source):
assert rewrite.RewriteRule(source)
@pytest.mark.parametrize('source', [
# Incomplete rules
{},
{'if': ['.account in Equity']},
{'a': ['.account = Income:Other'], 'b': ['.account = Expenses:Other']},
# Condition/assignment mixup
{'if': ['.account = Equity:Other'], 'then': ['equity-type = other']},
{'if': ['.account == Equity:Other'], 'then': ['equity-type != other']},
# Cross-category account assignment
{'if': ['.date >= 2020-01-01'], 'then': ['.account = Assets:Cash']},
{'if': ['.account in Equity'], 'then': ['.account = Assets:Cash']},
# Number reallocation != 1
{'if': ['.date >= 2020-01-01'], 'then': ['.number *= .5']},
{'if': ['.date >= 2020-01-01'], 'a': ['k1=v1'], 'b': ['k2=v2']},
# Date assignment
{'if': ['.date == 2020-01-01'], 'then': ['.date = 2020-02-02']},
# Redundant assignments
{'if': ['.account in Income'],
'then': ['.account = Income:Other', '.account = Income:Other']},
{'if': ['.number > 0'],
'a': ['.number *= .5', '.number *= .5'],
'b': ['.number *= .5']},
def test_invalid_rewrite_rule(source):
with pytest.raises(errors.RewriteRuleValidationError):
rewrite.RewriteRule(source)
def test_rewrite_ruleset():
account = 'Income:CurrencyConversion'
ruleset = rewrite.RewriteRuleset(rewrite.RewriteRule(src) for src in [
{'if': ['.account == Expenses:CurrencyConversion'],
'rename': [f'.account = {account}']},
{'if': ['project == alpha', '.account != Assets:Cash'],
'cap': ['project = Alpha']},
txn = testutil.Transaction(project='alpha', postings=[
('Assets:Cash', -20),
@@ -324,33 +319,33 @@ def test_rewrite_ruleset():
def test_ruleset_from_yaml_path():
yaml_path = testutil.test_path('userconfig/Rewrites01.yml')
assert rewrite.RewriteRuleset.from_yaml(yaml_path)
def test_ruleset_from_yaml_str():
with testutil.test_path('userconfig/Rewrites01.yml').open() as yaml_file:
yaml_s = yaml_file.read()
assert rewrite.RewriteRuleset.from_yaml(yaml_s)
def test_bad_ruleset_yaml_path():
yaml_path = testutil.test_path('repository/Projects/project-data.yml')
with pytest.raises(errors.RewriteRuleLoadError):
rewrite.RewriteRuleset.from_yaml(yaml_path)
# Wrong root objects
1,
2.3,
True,
None,
'string',
[{}, 'a'],
[{}, ['b']],
['a'],
[['b']],
# Rules have wrong type
[{'if': '.account in Equity', 'add': ['testkey = value']}],
[{'if': ['.account in Equity'], 'add': 'testkey = value'}],
def test_bad_ruleset_yaml_str(source):
yaml_doc = yaml.safe_dump(source)
rewrite.RewriteRuleset.from_yaml(yaml_doc)