Files @ 2840a64215bf
Branch filter:

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

dimesio
Add new payroll type codes

Oregon added a new payroll tax for disability so we need to add the
payroll-types for Ohio's state and local taxes.
"""test_filters - Unit tests for filter functions"""
# 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

import pytest

from . import testutil

from datetime import date

from beancount.core import data as bc_data

from conservancy_beancount import data
from conservancy_beancount import filters

MISSING_POSTING = testutil.Posting('<Missing Posting>', 0)

@pytest.fixture
def cc_txn_pair():
    dates = testutil.date_seq()
    txn_meta = {
        'payee': 'Smith-Dakota',
        'rt-id': 'rt:550',
    }
    return [
        testutil.Transaction(
            **txn_meta,
            date=next(dates),
            receipt='CCReceipt.pdf',
            metadate=next(dates),
            postings=[
                ('Liabilities:CreditCard', -36),
                ('Expenses:Other', 35),
                ('Expenses:Tax:Sales', 1),
            ],
        ),
        testutil.Transaction(
            **txn_meta,
            date=next(dates),
            receipt='CCPayment.pdf',
            metadate=next(dates),
            postings=[
                ('Liabilities:CreditCard', 36),
                ('Assets:Checking', -36, {'statement': 'CheckingStatement.pdf'}),
            ],
        ),
    ]

def check_filter(actual, entries, expected_indexes):
    postings = [post for txn in entries for post in txn.postings]
    expected = (postings[ii] for ii in expected_indexes)
    for actual_post, expected_post in itertools.zip_longest(
            actual, expected, fillvalue=MISSING_POSTING,
    ):
        assert actual_post[:-1] == expected_post[:-1]

@pytest.mark.parametrize('key,value,expected_indexes', [
    ('entity', 'Smith-Dakota', range(5)),
    ('receipt', 'CCReceipt.pdf', range(3)),
    ('receipt', 'CCPayment.pdf', range(3, 5)),
    ('receipt', 'CC', ()),
    ('statement', 'CheckingStatement.pdf', [4]),
    ('metadate', date(2020, 9, 2), range(3)),
    ('metadate', date(2020, 9, 4), range(3, 5)),
    ('BadKey', '', ()),
    ('emptykey', '', ()),
])
def test_filter_meta_equal(cc_txn_pair, key, value, expected_indexes):
    postings = data.Posting.from_entries(cc_txn_pair)
    actual = filters.filter_meta_equal(postings, key, value)
    check_filter(actual, cc_txn_pair, expected_indexes)

@pytest.mark.parametrize('key,regexp,expected_indexes', [
    ('entity', '^Smith-', range(5)),
    ('receipt', r'\.pdf$', range(5)),
    ('receipt', 'Receipt', range(3)),
    ('statement', '.', [4]),
    ('metadate', 'foo', ()),
    ('BadKey', '.', ()),
    ('emptykey', '.', ()),
])
def test_filter_meta_match(cc_txn_pair, key, regexp, expected_indexes):
    postings = data.Posting.from_entries(cc_txn_pair)
    actual = filters.filter_meta_match(postings, key, regexp)
    check_filter(actual, cc_txn_pair, expected_indexes)

@pytest.mark.parametrize('ticket_id,expected_indexes', [
    (550, range(5)),
    ('550', range(5)),
    (55, ()),
    ('55', ()),
    (50, ()),
    ('.', ()),
])
def test_filter_for_rt_id(cc_txn_pair, ticket_id, expected_indexes):
    postings = data.Posting.from_entries(cc_txn_pair)
    actual = filters.filter_for_rt_id(postings, ticket_id)
    check_filter(actual, cc_txn_pair, expected_indexes)

@pytest.mark.parametrize('rt_id', [
    'rt:450/',
    ' rt:450 rt:540',
    'rt://ticket/450',
    'rt://ticket/450/',
    ' rt://ticket/450',
    'rt://ticket/450 rt://ticket/540',
])
def test_filter_for_rt_id_syntax_variations(rt_id):
    entries = [testutil.Transaction(**{'rt-id': rt_id}, postings=[
        ('Income:Donations', -10),
        ('Assets:Cash', 10),
    ])]
    postings = data.Posting.from_entries(entries)
    actual = filters.filter_for_rt_id(postings, 450)
    check_filter(actual, entries, range(2))

def test_filter_for_rt_id_uses_first_link_only():
    entries = [testutil.Transaction(postings=[
        ('Income:Donations', -10, {'rt-id': 'rt:1 rt:350'}),
        ('Assets:Cash', 10, {'rt-id': 'rt://ticket/2 rt://ticket/350'}),
    ])]
    postings = data.Posting.from_entries(entries)
    actual = filters.filter_for_rt_id(postings, 350)
    check_filter(actual, entries, ()),

@pytest.mark.parametrize('opening_txn', [
    testutil.OpeningBalance(),
    None,
])
def test_remove_opening_balance_txn(opening_txn):
    entries = [
        testutil.Transaction(postings=[
            (account, amount),
            ('Assets:Checking', -amount),
        ])
        for account, amount in [
                ('Income:Donations', -50),
                ('Expenses:Other', 75),
        ]]
    if opening_txn is not None:
        entries.insert(1, opening_txn)
    actual = filters.remove_opening_balance_txn(entries)
    assert actual is opening_txn
    assert opening_txn not in entries
    assert not any(
        post.account.startswith('Equity:')
        for entry in entries
        for post in getattr(entry, 'postings', ())
    )

@pytest.mark.parametrize('entry', [
    bc_data.Custom({}, testutil.FY_START_DATE, 'conservancy_beancount_audit', []),
    None,
])
def test_audit_date(entry):
    dates = testutil.date_seq()
    entries = [
        bc_data.Open({}, next(dates), 'Income:Donations', ['USD'], None),
        bc_data.Open({}, next(dates), 'Assets:Cash', ['USD'], None),
        testutil.Transaction(postings=[
            ('Income:Donations', -10),
            ('Assets:Cash', 10),
        ]),
    ]
    if entry is not None:
        entries.append(entry)
    actual = filters.audit_date(entries)
    if entry is None:
        assert actual is None
    else:
        assert actual == entry.date

def test_iter_unique():
    assert list(filters.iter_unique('1213231')) == list('123')