Files @ 582316ae8564
Branch filter:

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

Brett Smith
data: Add Metadata.report_links() method.

This is just a variant of get_links() that eats TypeError.
Higher-level reporting code often wants it.
"""Test link checker for repository documents"""
# 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 itertools

from pathlib import Path

import pytest

from . import testutil

from conservancy_beancount import errors as errormod
from conservancy_beancount.plugin import meta_repo_links

METADATA_KEYS = [
    'approval',
    'check',
    'contract',
    'invoice',
    'purchase-order',
    'receipt',
    'statement',
]

GOOD_LINKS = [Path(s) for s in [
    'Projects/project-data.yml',
    'Projects/project-list.yml',
]]

BAD_LINKS = [Path(s) for s in [
    'NonexistentDirectory/NonexistentFile1.txt',
    'NonexistentDirectory/NonexistentFile2.txt',
]]

NOT_FOUND_MSG = '{} not found in repository: {}'.format

def build_meta(keys=None, *sources):
    if keys is None:
        keys = iter(METADATA_KEYS)
    sources = (itertools.cycle(src) for src in sources)
    return {key: ' '.join(str(x) for x in rest)
            for key, *rest in zip(keys, *sources)}

@pytest.fixture(scope='module')
def hook():
    config = testutil.TestConfig(repo_path='repository')
    return meta_repo_links.MetaRepoLinks(config)

def test_error_with_no_repository():
    config = testutil.TestConfig(repo_path=None)
    with pytest.raises(errormod.ConfigurationError):
        meta_repo_links.MetaRepoLinks(config)

def test_good_txn_links(hook):
    meta = build_meta(None, GOOD_LINKS)
    txn = testutil.Transaction(**meta, postings=[
        ('Income:Donations', -5),
        ('Assets:Cash', 5),
    ])
    assert not list(hook.run(txn))

def test_good_post_links(hook):
    meta = build_meta(None, GOOD_LINKS)
    txn = testutil.Transaction(postings=[
        ('Income:Donations', -5, meta),
        ('Assets:Cash', 5),
    ])
    assert not list(hook.run(txn))

def test_bad_txn_links(hook):
    meta = build_meta(None, BAD_LINKS)
    txn = testutil.Transaction(**meta, postings=[
        ('Income:Donations', -5),
        ('Assets:Cash', 5),
    ])
    expected = {NOT_FOUND_MSG(key, value) for key, value in meta.items()}
    actual = {error.message for error in hook.run(txn)}
    assert expected == actual

def test_bad_post_links(hook):
    meta = build_meta(None, BAD_LINKS)
    txn = testutil.Transaction(postings=[
        ('Income:Donations', -5, meta.copy()),
        ('Assets:Cash', 5),
    ])
    expected = {NOT_FOUND_MSG(key, value) for key, value in meta.items()}
    actual = {error.message for error in hook.run(txn)}
    assert expected == actual

def test_flagged_txn_not_checked(hook):
    keys = iter(METADATA_KEYS)
    txn_meta = build_meta(keys, BAD_LINKS)
    txn_meta['flag'] = '!'
    txn = testutil.Transaction(**txn_meta, postings=[
        ('Income:Donations', -5, build_meta(keys, BAD_LINKS)),
        ('Assets:Checking', 5, build_meta(keys, BAD_LINKS)),
    ])
    assert not list(hook.run(txn))

@pytest.mark.parametrize('value', testutil.NON_STRING_METADATA_VALUES)
def test_bad_metadata_type(hook, value):
    txn = testutil.Transaction(**{'check': value}, postings=[
        ('Income:Donations', -5),
        ('Assets:Cash', 5),
    ])
    expected = {'transaction has wrong type of check: expected str but is a {}'.format(
        type(value).__name__,
    )}
    actual = {error.message for error in hook.run(txn)}
    assert expected == actual

@pytest.mark.parametrize('ext_doc', [
    'rt:123',
    'rt:456/789',
    'rt://ticket/23',
    'rt://ticket/34/attachments/567890',
])
def test_docs_outside_repository_not_checked(hook, ext_doc):
    txn = testutil.Transaction(
        receipt='{} {} {}'.format(GOOD_LINKS[0], ext_doc, BAD_LINKS[1]),
        postings=[
            ('Income:Donations', -5),
            ('Assets:Cash', 5),
        ])
    expected = {NOT_FOUND_MSG('receipt', BAD_LINKS[1])}
    actual = {error.message for error in hook.run(txn)}
    assert expected == actual

def test_mixed_results(hook):
    txn = testutil.Transaction(
        approval='{} {}'.format(*GOOD_LINKS),
        contract='{} {}'.format(BAD_LINKS[0], GOOD_LINKS[1]),
        postings=[
            ('Income:Donations', -5, {'invoice': '{} {}'.format(*BAD_LINKS)}),
            ('Assets:Cash', 5, {'statement': '{} {}'.format(GOOD_LINKS[0], BAD_LINKS[1])}),
        ])
    expected = {
        NOT_FOUND_MSG('contract', BAD_LINKS[0]),
        NOT_FOUND_MSG('invoice', BAD_LINKS[0]),
        NOT_FOUND_MSG('invoice', BAD_LINKS[1]),
        NOT_FOUND_MSG('statement', BAD_LINKS[1]),
    }
    actual = {error.message for error in hook.run(txn)}
    assert expected == actual