Files @ 638bc8ccbb77
Branch filter:

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

Brett Smith
accrual: Make comment not an actual type:ignore.
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
5e9e11923e8c
5e9e11923e8c
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
5e9e11923e8c
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
e26dffa21428
7a9bc2da5040
7a9bc2da5040
5e9e11923e8c
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
5e9e11923e8c
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
5e9e11923e8c
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
5e9e11923e8c
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
7a9bc2da5040
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
5e9e11923e8c
"""test_reports_core - Unit tests for basic reports functions"""
# 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 pytest

from decimal import Decimal

from . import testutil

from conservancy_beancount import data
from conservancy_beancount.reports import core

AMOUNTS = [
    2,
    Decimal('4.40'),
    testutil.Amount('6.60', 'CHF'),
    core.Balance([testutil.Amount('8.80')]),
]

@pytest.fixture
def balance_postings():
    dates = testutil.date_seq(testutil.FY_MID_DATE)
    return data.Posting.from_entries([
        testutil.Transaction(date=next(dates), postings=[
            ('Equity:OpeningBalance', -1000),
            ('Assets:Checking', 1000),
        ]),
        testutil.Transaction(date=next(dates), postings=[
            ('Income:Donations', -10),
            ('Expenses:BankingFees', 1),
            ('Assets:Checking', 9),
        ]),
        testutil.Transaction(date=next(dates), postings=[
            ('Income:Donations', -20),
            ('Expenses:Services:Fundraising', 1),
            ('Equity:Realized:CurrencyConversion', 1),
            ('Assets:Checking', 18),
        ]),
    ])

@pytest.mark.parametrize('acct_name', [
    'Assets:Checking',
    'Assets:Receivable:Accounts',
    'Expenses:Other',
    'Expenses:FilingFees',
])
def test_normalize_amount_func_pos(acct_name):
    actual = core.normalize_amount_func(acct_name)
    for amount in AMOUNTS:
        assert actual(amount) == amount

@pytest.mark.parametrize('acct_name', [
    'Equity:Funds:Restricted',
    'Equity:Realized:CurrencyConversion',
    'Income:Donations',
    'Income:Other',
    'Liabilities:CreditCard',
    'Liabilities:Payable:Accounts',
])
def test_normalize_amount_func_neg(acct_name):
    actual = core.normalize_amount_func(acct_name)
    for amount in AMOUNTS:
        assert actual(amount) == -amount

@pytest.mark.parametrize('acct_name', [
    '',
    'Assets',
    'Equity',
    'Expenses',
    'Income',
    'Liabilities',
])
def test_normalize_amount_func_bad_acct_name(acct_name):
    with pytest.raises(ValueError):
        core.normalize_amount_func(acct_name)

def test_sort_and_filter_accounts():
    accounts = (data.Account(s) for s in [
        'Expenses:Services',
        'Assets:Receivable',
        'Income:Other',
        'Liabilities:Payable',
        'Equity:Funds:Unrestricted',
        'Income:Donations',
        'Expenses:Other',
    ])
    actual = core.sort_and_filter_accounts(accounts, ['Equity', 'Income', 'Expenses'])
    assert list(actual) == [
        (0, 'Equity:Funds:Unrestricted'),
        (1, 'Income:Donations'),
        (1, 'Income:Other'),
        (2, 'Expenses:Other'),
        (2, 'Expenses:Services'),
    ]

def test_sort_and_filter_accounts_unused_name():
    accounts = (data.Account(s) for s in [
        'Liabilities:CreditCard',
        'Assets:Cash',
        'Assets:Receivable:Accounts',
    ])
    actual = core.sort_and_filter_accounts(
        accounts, ['Assets:Receivable', 'Liabilities:Payable', 'Assets', 'Liabilities'],
    )
    assert list(actual) == [
        (0, 'Assets:Receivable:Accounts'),
        (2, 'Assets:Cash'),
        (3, 'Liabilities:CreditCard'),
    ]

def test_sort_and_filter_accounts_with_subaccounts():
    accounts = (data.Account(s) for s in [
        'Assets:Checking',
        'Assets:Receivable:Fraud',
        'Assets:Cash',
        'Assets:Receivable:Accounts',
    ])
    actual = core.sort_and_filter_accounts(accounts, ['Assets:Receivable', 'Assets'])
    assert list(actual) == [
        (0, 'Assets:Receivable:Accounts'),
        (0, 'Assets:Receivable:Fraud'),
        (1, 'Assets:Cash'),
        (1, 'Assets:Checking'),
    ]

@pytest.mark.parametrize('empty_arg', ['accounts', 'order'])
def test_sort_and_filter_accounts_empty_accounts(empty_arg):
    accounts = [data.Account(s) for s in ['Expenses:Other', 'Income:Other']]
    if empty_arg == 'accounts':
        args = ([], accounts)
    else:
        args = (accounts, [])
    actual = core.sort_and_filter_accounts(*args)
    assert next(actual, None) is None

def check_account_balance(balance_seq, account, balance):
    assert next(balance_seq, None) == (account, {'USD': testutil.Amount(balance)})

@pytest.mark.parametrize('days_after', range(4))
def test_account_balances(balance_postings, days_after):
    start_date = testutil.FY_MID_DATE + datetime.timedelta(days=days_after)
    balance_cls = core.PeriodPostings.with_start_date(start_date)
    groups = dict(balance_cls.group_by_account(balance_postings))
    actual = core.account_balances(groups)
    expect_opening = -1027
    opening_acct, opening_bal = next(actual)
    if days_after < 1:
        check_account_balance(actual, 'Equity:OpeningBalance', -1000)
        expect_opening += 1000
    if days_after < 3:
        check_account_balance(actual, 'Equity:Realized:CurrencyConversion', 1)
        expect_opening -= 1
    if days_after < 2:
        check_account_balance(actual, 'Income:Donations', -30)
        expect_opening += 30
    elif days_after < 3:
        check_account_balance(actual, 'Income:Donations', -20)
        expect_opening += 20
    if days_after < 2:
        check_account_balance(actual, 'Expenses:BankingFees', 1)
        expect_opening -= 1
    if days_after < 3:
        check_account_balance(actual, 'Expenses:Services:Fundraising', 1)
        expect_opening -= 1
    if expect_opening:
        assert opening_bal == {'USD': testutil.Amount(expect_opening)}
    else:
        assert opening_bal.is_zero()
    assert opening_acct == core.OPENING_BALANCE_NAME
    check_account_balance(actual, core.ENDING_BALANCE_NAME, -1027)
    assert next(actual, None) is None

def test_account_balances_order_arg(balance_postings):
    start_date = testutil.FY_MID_DATE + datetime.timedelta(days=1)
    balance_cls = core.PeriodPostings.with_start_date(start_date)
    groups = dict(balance_cls.group_by_account(balance_postings))
    actual = core.account_balances(groups, ['Income', 'Assets'])
    check_account_balance(actual, core.OPENING_BALANCE_NAME, 1000)
    check_account_balance(actual, 'Income:Donations', -30)
    check_account_balance(actual, 'Assets:Checking', 27)
    check_account_balance(actual, core.ENDING_BALANCE_NAME, 997)
    assert next(actual, None) is None

def test_account_balances_order_filters_all(balance_postings):
    start_date = testutil.FY_MID_DATE + datetime.timedelta(days=1)
    balance_cls = core.PeriodPostings.with_start_date(start_date)
    groups = dict(balance_cls.group_by_account(balance_postings))
    actual = core.account_balances(groups, ['Liabilities'])
    account, balance = next(actual)
    assert account is core.OPENING_BALANCE_NAME
    assert balance.is_zero()
    account, balance = next(actual)
    assert account is core.ENDING_BALANCE_NAME
    assert balance.is_zero()

def test_account_balances_empty_postings():
    actual = core.account_balances({})
    account, balance = next(actual)
    assert account is core.OPENING_BALANCE_NAME
    assert balance.is_zero()
    account, balance = next(actual)
    assert account is core.ENDING_BALANCE_NAME
    assert balance.is_zero()