114 lines
5.2 KiB
Python
114 lines
5.2 KiB
Python
# Copyright (c) 2026 Patrick Motsch
|
|
# All rights reserved.
|
|
"""Unit tests for the Bexio connector's getAccountBalances aggregation logic."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from modules.features.trustee.accounting.connectors.accountingConnectorBexio import (
|
|
AccountingConnectorBexio,
|
|
_isIncomeStatementAccount,
|
|
)
|
|
|
|
|
|
class TestIsIncomeStatementAccount:
|
|
@pytest.mark.parametrize("accno,expected", [
|
|
("1020", False), ("2010", False), ("3000", True), ("9999", True), ("", False),
|
|
])
|
|
def test_classification(self, accno, expected):
|
|
assert _isIncomeStatementAccount(accno) == expected
|
|
|
|
|
|
class TestBexioGetAccountBalances:
|
|
@pytest.mark.asyncio
|
|
async def test_aggregatesBalanceSheetAccount_cumulativeAcrossMonths(self):
|
|
connector = AccountingConnectorBexio()
|
|
|
|
accounts = [{"id": 100, "account_no": "1020"}, {"id": 200, "account_no": "6000"}]
|
|
|
|
# Simulate a clean year for account 1020 (BS): +1000 in Jan, -300 in Feb, +500 in Dec
|
|
rawJournal = [
|
|
{"date": "2025-01-15", "amount": 1000.0, "debit_account_id": 100, "credit_account_id": 200},
|
|
{"date": "2025-02-10", "amount": 300.0, "debit_account_id": 200, "credit_account_id": 100},
|
|
{"date": "2025-12-20", "amount": 500.0, "debit_account_id": 100, "credit_account_id": 200},
|
|
]
|
|
|
|
async def _fakeAccounts(self, config):
|
|
return accounts
|
|
|
|
async def _fakeJournal(self, config, dateTo):
|
|
return rawJournal
|
|
|
|
with patch.object(AccountingConnectorBexio, "_loadRawAccounts", _fakeAccounts), \
|
|
patch.object(AccountingConnectorBexio, "_fetchAllJournalRows", _fakeJournal):
|
|
balances = await connector.getAccountBalances({"accessToken": "x", "apiBaseUrl": "http://x"}, years=[2025])
|
|
|
|
byPeriod = {(b.accountNumber, b.periodMonth): b for b in balances if b.periodYear == 2025}
|
|
|
|
# Account 1020 (BS) cumulative: Jan +1000, Feb +1000-300=700, Dec +700+500=1200
|
|
assert byPeriod[("1020", 1)].closingBalance == 1000.0
|
|
assert byPeriod[("1020", 2)].closingBalance == 700.0
|
|
assert byPeriod[("1020", 11)].closingBalance == 700.0
|
|
assert byPeriod[("1020", 12)].closingBalance == 1200.0
|
|
assert byPeriod[("1020", 0)].closingBalance == 1200.0 # annual
|
|
assert byPeriod[("1020", 0)].openingBalance == 0.0
|
|
assert byPeriod[("1020", 1)].openingBalance == 0.0
|
|
assert byPeriod[("1020", 2)].openingBalance == 1000.0 # = previous month's closing
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_balanceSheetAccount_carriesPriorYearOpening(self):
|
|
connector = AccountingConnectorBexio()
|
|
accounts = [{"id": 100, "account_no": "1020"}, {"id": 200, "account_no": "6000"}]
|
|
|
|
rawJournal = [
|
|
{"date": "2024-06-01", "amount": 5000.0, "debit_account_id": 100, "credit_account_id": 200},
|
|
{"date": "2025-03-15", "amount": 1000.0, "debit_account_id": 100, "credit_account_id": 200},
|
|
]
|
|
|
|
async def _fakeAccounts(self, config):
|
|
return accounts
|
|
|
|
async def _fakeJournal(self, config, dateTo):
|
|
return rawJournal
|
|
|
|
with patch.object(AccountingConnectorBexio, "_loadRawAccounts", _fakeAccounts), \
|
|
patch.object(AccountingConnectorBexio, "_fetchAllJournalRows", _fakeJournal):
|
|
balances = await connector.getAccountBalances({"accessToken": "x", "apiBaseUrl": "http://x"}, years=[2025])
|
|
|
|
byPeriod = {(b.accountNumber, b.periodMonth): b for b in balances if b.periodYear == 2025}
|
|
|
|
# 2025 opening for 1020 = 5000 (carried over from 2024)
|
|
assert byPeriod[("1020", 1)].openingBalance == 5000.0
|
|
assert byPeriod[("1020", 0)].openingBalance == 5000.0
|
|
assert byPeriod[("1020", 12)].closingBalance == 6000.0
|
|
assert byPeriod[("1020", 0)].closingBalance == 6000.0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_incomeStatementAccount_resetsToZeroEachYear(self):
|
|
connector = AccountingConnectorBexio()
|
|
accounts = [{"id": 200, "account_no": "6000"}, {"id": 300, "account_no": "1020"}]
|
|
|
|
rawJournal = [
|
|
{"date": "2024-12-31", "amount": 99999.99, "debit_account_id": 200, "credit_account_id": 300},
|
|
{"date": "2025-06-15", "amount": 250.0, "debit_account_id": 200, "credit_account_id": 300},
|
|
]
|
|
|
|
async def _fakeAccounts(self, config):
|
|
return accounts
|
|
|
|
async def _fakeJournal(self, config, dateTo):
|
|
return rawJournal
|
|
|
|
with patch.object(AccountingConnectorBexio, "_loadRawAccounts", _fakeAccounts), \
|
|
patch.object(AccountingConnectorBexio, "_fetchAllJournalRows", _fakeJournal):
|
|
balances = await connector.getAccountBalances({"accessToken": "x", "apiBaseUrl": "http://x"}, years=[2025])
|
|
|
|
byPeriod = {(b.accountNumber, b.periodMonth): b for b in balances if b.periodYear == 2025}
|
|
|
|
# ER account 6000: prior year had 99999.99 movement; 2025 opening MUST be 0
|
|
assert byPeriod[("6000", 1)].openingBalance == 0.0
|
|
assert byPeriod[("6000", 0)].openingBalance == 0.0
|
|
assert byPeriod[("6000", 6)].closingBalance == 250.0
|
|
assert byPeriod[("6000", 12)].closingBalance == 250.0
|
|
assert byPeriod[("6000", 0)].closingBalance == 250.0
|