conservancy_beancount/tests/test_cliutil_searchterm.py
Brett Smith cd1b28ae3e cliutil: Add generalized SearchTerm class.
This makes the same filtering easily available to other reporting tools for
consistency.
2020-06-09 09:04:27 -04:00

164 lines
5.1 KiB
Python

"""test_cliutil_searchterm - Unit tests for cliutil.SearchTerm"""
# 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 re
import pytest
from . import testutil
from conservancy_beancount import cliutil
from conservancy_beancount import data
TICKET_LINKS = [
'123',
'rt:123',
'rt://ticket/123',
]
ATTACHMENT_LINKS = [
'123/456',
'rt:123/456',
'rt://ticket/123/attachments/456',
]
REPOSITORY_LINKS = [
'789.pdf',
'Documents/789.pdf',
]
RT_LINKS = TICKET_LINKS + ATTACHMENT_LINKS
ALL_LINKS = RT_LINKS + REPOSITORY_LINKS
INVALID_METADATA_KEYS = [
# Must start with a-z
';key',
' key',
'ákey',
'Key',
# Must only contain alphanumerics, -, and _
'key;',
'key ',
'a.key',
]
@pytest.fixture(scope='module')
def defaults_parser():
return cliutil.SearchTerm.arg_parser('document', 'rt-id')
@pytest.fixture(scope='module')
def one_default_parser():
return cliutil.SearchTerm.arg_parser('default')
@pytest.fixture(scope='module')
def no_default_parser():
return cliutil.SearchTerm.arg_parser()
def check_link_regexp(regexp, match_s, first_link_only=False):
assert regexp
assert re.search(regexp, match_s)
assert re.search(regexp, match_s + ' postlink')
assert re.search(regexp, match_s + '0') is None
assert re.search(regexp, '1' + match_s) is None
end_match = re.search(regexp, 'prelink ' + match_s)
if first_link_only:
assert end_match is None
else:
assert end_match
@pytest.mark.parametrize('arg', TICKET_LINKS)
def test_search_term_parse_ticket(defaults_parser, arg):
key, regexp = defaults_parser(arg)
assert key == 'rt-id'
check_link_regexp(regexp, 'rt:123', first_link_only=True)
check_link_regexp(regexp, 'rt://ticket/123', first_link_only=True)
@pytest.mark.parametrize('arg', ATTACHMENT_LINKS)
def test_search_term_parse_attachment(defaults_parser, arg):
key, regexp = defaults_parser(arg)
assert key == 'document'
check_link_regexp(regexp, 'rt:123/456')
check_link_regexp(regexp, 'rt://ticket/123/attachments/456')
@pytest.mark.parametrize('key,query', testutil.combine_values(
['approval', 'contract', 'invoice'],
RT_LINKS,
))
def test_search_term_parse_metadata_rt_shortcut(defaults_parser, key, query):
actual_key, regexp = defaults_parser(f'{key}={query}')
assert actual_key == key
if query.endswith('/456'):
check_link_regexp(regexp, 'rt:123/456')
check_link_regexp(regexp, 'rt://ticket/123/attachments/456')
else:
check_link_regexp(regexp, 'rt:123')
check_link_regexp(regexp, 'rt://ticket/123')
@pytest.mark.parametrize('search_prefix', [
'',
'approval=',
'contract=',
'invoice=',
])
def test_search_term_parse_repo_link(defaults_parser, search_prefix):
document = '1234.pdf'
actual_key, regexp = defaults_parser(f'{search_prefix}{document}')
if search_prefix:
expect_key = search_prefix.rstrip('=')
else:
expect_key = 'document'
assert actual_key == expect_key
check_link_regexp(regexp, document)
@pytest.mark.parametrize('search,unmatched', [
('1234.pdf', '1234_pdf'),
])
def test_search_term_parse_regexp_escaping(defaults_parser, search, unmatched):
_, regexp = defaults_parser(search)
assert re.search(regexp, unmatched) is None
@pytest.mark.parametrize('key', INVALID_METADATA_KEYS)
def test_non_metadata_key(one_default_parser, key):
document = f'{key}=890'
key, pattern = one_default_parser(document)
assert key == 'default'
check_link_regexp(pattern, document)
@pytest.mark.parametrize('arg', ALL_LINKS)
def test_default_parser(one_default_parser, arg):
key, _ = one_default_parser(arg)
assert key == 'default'
@pytest.mark.parametrize('arg', ALL_LINKS + [
f'{nonkey}={link}' for nonkey, link in testutil.combine_values(
INVALID_METADATA_KEYS, ALL_LINKS,
)
])
def test_no_key(no_default_parser, arg):
with pytest.raises(ValueError):
key, pattern = no_default_parser(arg)
@pytest.mark.parametrize('key', ['zero', 'one', 'two'])
def test_filter_postings(key):
txn = testutil.Transaction(postings=[
('Income:Other', 3, {'one': '1', 'two': '2'}),
('Income:Other', 2, {'two': '2'}),
('Income:Other', 1, {'one': '1'}),
])
search = cliutil.SearchTerm(key, '.')
actual = list(search.filter_postings(data.Posting.from_txn(txn)))
assert len(actual) == 0 if key == 'zero' else 2
assert all(post.meta.get(key) for post in actual)