Files @ d9360f1ceafe
Branch filter:

Location: NPO-Accounting/conservancy_beancount/tests/test_reports_balance_sheet.py - annotation

Brett Smith
audit_report: Use concurrent.futures for parallelization.

This is basically a pure maintainability change: concurrent.futures is the
nicest API that's available in both Python 3.6 and 3.7, and our other tools
are using it.
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
29d4325c7abd
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
e05f55659a89
a87d4bfc6c0c
29d4325c7abd
a87d4bfc6c0c
aa1f2ea35aed
aa1f2ea35aed
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
a87d4bfc6c0c
"""test_reports_balance_sheet.py - Unit tests for balance sheet report"""
# 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 datetime
import io
import itertools

from decimal import Decimal

import pytest

from . import testutil

import odf.opendocument

from beancount.core.data import Open

from conservancy_beancount import data
from conservancy_beancount.reports import balance_sheet

Fund = balance_sheet.Fund
Period = balance_sheet.Period

clean_account_meta = pytest.fixture(scope='module')(testutil.clean_account_meta)

@pytest.fixture(scope='module')
def income_expense_balances():
    txns = []
    prior_date = datetime.date(2019, 2, 2)
    period_date = datetime.date(2019, 4, 4)
    for (acct, post_type), fund in itertools.product([
            ('Income:Donations', 'Donations'),
            ('Income:Sales', 'RBI'),
            ('Expenses:Postage', 'fundraising'),
            ('Expenses:Postage', 'management'),
            ('Expenses:Postage', 'program'),
            ('Expenses:Services', 'fundraising'),
            ('Expenses:Services', 'program'),
    ], ['Conservancy', 'Alpha']):
        root_acct, _, classification = acct.partition(':')
        try:
            data.Account(acct).meta
        except KeyError:
            data.Account.load_opening(Open(
                {'classification': classification},
                datetime.date(2000, 1, 1),
                acct, None, None,
            ))
        meta = {
            'project': fund,
            f'{root_acct.lower().rstrip("s")}-type': post_type,
        }
        sign = '' if root_acct == 'Expenses' else '-'
        txns.append(testutil.Transaction(date=prior_date, postings=[
            (acct, f'{sign}2.40', meta),
        ]))
        txns.append(testutil.Transaction(date=period_date, postings=[
            (acct, f'{sign}2.60', meta),
        ]))
    return balance_sheet.Balances(
        data.Posting.from_entries(txns),
        datetime.date(2019, 3, 1),
        datetime.date(2020, 3, 1),
    )

@pytest.mark.parametrize('kwargs,expected', [
    ({'account': 'Income:Donations'}, -10),
    ({'account': 'Income'}, -20),
    ({'account': 'Income:Nonexistent'}, None),
    ({'classification': 'Postage'}, 30),
    ({'classification': 'Services'}, 20),
    ({'classification': 'Nonexistent'}, None),
    ({'period': Period.PRIOR, 'account': 'Income'}, '-9.60'),
    ({'period': Period.PERIOD, 'account': 'Expenses'}, 26),
    ({'fund': Fund.RESTRICTED, 'account': 'Income'}, -10),
    ({'fund': Fund.UNRESTRICTED, 'account': 'Expenses'}, 25),
    ({'post_type': 'fundraising'}, 20),
    ({'post_type': 'management'}, 10),
    ({'post_type': 'Nonexistent'}, None),
    ({'period': Period.PRIOR, 'post_type': 'fundraising'}, '9.60'),
    ({'fund': Fund.RESTRICTED, 'post_type': 'program'}, 10),
    ({'period': Period.PRIOR, 'fund': Fund.RESTRICTED, 'post_type': 'program'}, '4.80'),
    ({'period': Period.PERIOD, 'fund': Fund.RESTRICTED, 'post_type': 'ø'}, None),
    ({'account': ('Income', 'Expenses')}, 30),
    ({'account': ('Income', 'Expenses'), 'fund': Fund.UNRESTRICTED}, 15),
])
def test_balance_total(income_expense_balances, kwargs, expected):
    actual = income_expense_balances.total(**kwargs)
    if expected is None:
        assert not actual
    else:
        assert actual == {'USD': testutil.Amount(expected)}

def run_main(arglist=[], config=None):
    if config is None:
        config = testutil.TestConfig(books_path=testutil.test_path('books/fund.beancount'))
    stdout = io.BytesIO()
    stderr = io.StringIO()
    retcode = balance_sheet.main(['-O', '-'] + arglist, stdout, stderr, config)
    stdout.seek(0)
    stderr.seek(0)
    return retcode, stdout, stderr

def test_main():
    retcode, stdout, stderr = run_main()
    assert retcode == 0
    assert not stderr.getvalue()
    report = odf.opendocument.load(stdout)
    assert report.spreadsheet.childNodes