diff --git a/tests/test_reports_balance_sheet.py b/tests/test_reports_balance_sheet.py new file mode 100644 index 0000000000000000000000000000000000000000..5239a766eb0fa1261065b21db44097a3fdf42d43 --- /dev/null +++ b/tests/test_reports_balance_sheet.py @@ -0,0 +1,122 @@ +"""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 . + +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.OPENING, 'account': 'Income'}, '-9.60'), + ({'period': Period.PERIOD, 'account': 'Expenses'}, 26), + ({'fund': Fund.RESTRICTED, 'account': 'Income'}, -10), + ({'fund': Fund.UNRESTRICTED, 'account': 'Expenses'}, 25), + ({'post_type': 'Donations'}, -10), + ({'post_type': 'fundraising'}, 20), + ({'post_type': 'management'}, 10), + ({'post_type': 'Nonexistent'}, None), + ({'period': Period.OPENING, 'post_type': 'RBI'}, '-4.80'), + ({'fund': Fund.RESTRICTED, 'post_type': 'program'}, 10), + ({'period': Period.PERIOD, 'fund': Fund.UNRESTRICTED, 'post_type': 'RBI'}, '-2.60'), + ({'period': Period.OPENING, 'fund': Fund.RESTRICTED, 'post_type': 'program'}, '4.80'), + ({'period': Period.PERIOD, 'fund': Fund.RESTRICTED, 'post_type': 'ø'}, None), +]) +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