Files @ e79877ee6a3f
Branch filter:

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

Brett Smith
data: Add rt-id to LINK_METADATA.

This gets closer to our real intentions: anything that checks link
metadata should check rt-id. MetaRepoLinks is the exception, not
the rule, in ignoring rt-id.
"""Test validation of entity metadata"""
# Copyright © 2020  Brett Smith
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

import pytest

from . import testutil

from conservancy_beancount.plugin import meta_entity

VALID_VALUES = {
    # Classic entity: LastName-FirstName
    'Smith-Alex',
    # Various people and companies have one-word names
    # Digits are allowed, as part of a name or standalone
    'Company19',
    'Company-19',
    # No case requirements
    'boyd-danah',
    # No limit on the number of parts of the name
    'B-van-der-A',
    # Names that have no ASCII are allowed, with or without dash separators
    '田中流星',
    '田中-流星',
    'スミスダコタ',
    'スミス-ダコタ',
    'Яшин-Данила',
    # The PayPal importer produces . in entity metadata
    'Du-Bois-W.-E.-B.',
    # import2ledger produces entities that end with -
    # That's probably a bug, but allow it for now.
    'foo-',
}

INVALID_VALUES = {
    # Starting with a - is not allowed
    '-foo',
    '-',
    # Names that can be reduced to ASCII should be
    # Producers should change this to Uberentity or Ueberentity
    # I am not wild about this rule and would like to relax it—it's mostly
    # based on an expectation that entities are typed in by an American. That's
    # true less and less and it seems like we should reduce the amount of
    # mangling producers are expected to do. But it's the rule for today.
    'Überentity',
    # Whitespace is never allowed
    ' ',
    'Alex Smith',
    '田中\u00A0流星',  # Non-breaking space
    # The only punctuation allowed is - and .
    'スミス_ダコタ',
    'Яшин—Данила',  # em dash
    # An empty string is not valid
    '',
}

TEST_KEY = 'entity'

@pytest.fixture(scope='module')
def hook():
    config = testutil.TestConfig()
    return meta_entity.MetaEntity(config)

@pytest.mark.parametrize('src_value', VALID_VALUES)
def test_valid_values_on_postings(hook, src_value):
    txn = testutil.Transaction(postings=[
        ('Assets:Cash', -25),
        ('Expenses:General', 25, {TEST_KEY: src_value}),
    ])
    assert not any(hook.run(txn))

@pytest.mark.parametrize('src_value', INVALID_VALUES)
def test_invalid_values_on_postings(hook, src_value):
    txn = testutil.Transaction(postings=[
        ('Assets:Cash', -25),
        ('Expenses:General', 25, {TEST_KEY: src_value}),
    ])
    errors = list(hook.run(txn))
    assert len(errors) == 1
    assert errors[0].message == "Expenses:General has invalid entity: {}".format(src_value)

@pytest.mark.parametrize('src_value', VALID_VALUES)
def test_valid_values_on_transactions(hook, src_value):
    txn = testutil.Transaction(**{TEST_KEY: src_value}, postings=[
        ('Assets:Cash', -25),
        ('Expenses:General', 25),
    ])
    assert not any(hook.run(txn))

@pytest.mark.parametrize('src_value', INVALID_VALUES)
def test_invalid_values_on_transactions(hook, src_value):
    txn = testutil.Transaction(**{TEST_KEY: src_value}, postings=[
        ('Assets:Cash', -25),
        ('Expenses:General', 25),
    ])
    errors = list(hook.run(txn))
    assert 1 <= len(errors) <= 2
    assert all(error.message == "transaction has invalid entity: {}".format(src_value)
               for error in hook.run(txn))

@pytest.mark.parametrize('src_value', VALID_VALUES)
def test_valid_values_on_payee(hook, src_value):
    txn = testutil.Transaction(payee=src_value, postings=[
        ('Assets:Cash', -25),
        ('Expenses:General', 25),
    ])
    assert not any(hook.run(txn))

@pytest.mark.parametrize('src_value', INVALID_VALUES)
def test_invalid_values_on_payee(hook, src_value):
    txn = testutil.Transaction(payee=src_value, postings=[
        ('Assets:Cash', -25),
        ('Expenses:General', 25),
    ])
    errors = list(hook.run(txn))
    assert 1 <= len(errors) <= 2
    assert all(error.message == "transaction has invalid entity: {}".format(src_value)
               for error in hook.run(txn))

@pytest.mark.parametrize('payee,src_value', testutil.combine_values(
    INVALID_VALUES,
    VALID_VALUES,
))
def test_invalid_payee_but_valid_metadata(hook, payee, src_value):
    txn = testutil.Transaction(**{'payee': payee, TEST_KEY: src_value}, postings=[
        ('Assets:Cash', -25),
        ('Expenses:Other', 25),
    ])
    assert not any(hook.run(txn))

@pytest.mark.parametrize('account,required', [
    ('Assets:Bank:Checking', False),
    ('Assets:Cash', False),
    ('Assets:Receivable:Accounts', True),
    ('Assets:Receivable:Loans', True),
    ('Equity:OpeningBalances', False),
    ('Expenses:General', True),
    ('Income:Donations', True),
    ('Liabilities:CreditCard', False),
    ('Liabilities:Payable:Accounts', True),
    ('Liabilities:Payable:Vacation', True),
    ('Liabilities:UnearnedIncome:Donations', False),
])
def test_which_accounts_required_on(hook, account, required):
    txn = testutil.Transaction(postings=[
        ('Assets:Checking', -25),
        (account, 25),
    ])
    errors = list(hook.run(txn))
    if not required:
        assert not errors
    else:
        assert errors
        assert any(error.message == "{} missing entity".format(account)
                   for error in errors)

def test_not_required_on_opening(hook):
    txn = testutil.Transaction.opening_balance()
    assert not list(hook.run(txn))