"""test_cliutil_searchterm - Unit tests for cliutil.SearchTerm""" # 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 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)