gateway/tests/unit/features/trustee/test_accountingConnectorRma_balances.py
2026-04-26 08:31:35 +02:00

156 lines
5.7 KiB
Python

# Copyright (c) 2026 Patrick Motsch
# All rights reserved.
"""Unit tests for the RMA connector's getAccountBalances implementation.
Mocks the `_fetchSaldoRows` low-level call so we exercise the orchestration
logic (period iteration, ER/BS handling, opening/closing carry-over) without
hitting the real RMA HTTP API.
"""
import json
from typing import Dict
from unittest.mock import patch
import pytest
from modules.features.trustee.accounting.connectors.accountingConnectorRma import (
AccountingConnectorRma,
_formatLastDayOfMonth,
_isIncomeStatementAccount,
_parseSaldoBody,
)
class TestParseSaldoBody:
def test_jsonRowsParsed(self):
body = json.dumps({
"row": [
{"column": ["1020", "Bank UBS", "48507.4100"]},
{"column": ["6000", "Personalaufwand", "12000.00"]},
]
})
rows = _parseSaldoBody(body)
assert ("1020", 48507.41) in rows
assert ("6000", 12000.0) in rows
def test_xmlRowsParsed(self):
body = (
"<table>"
"<row><column>1020</column><column>Bank</column><column>48507.41</column></row>"
"<row><column>2010</column><column>AHV</column><column>-1234.50</column></row>"
"</table>"
)
rows = _parseSaldoBody(body)
assert ("1020", 48507.41) in rows
assert ("2010", -1234.5) in rows
def test_emptyAndMalformedReturnEmpty(self):
assert _parseSaldoBody("") == []
assert _parseSaldoBody("not even json or xml") == []
assert _parseSaldoBody('{"row": []}') == []
class TestIsIncomeStatementAccount:
@pytest.mark.parametrize("accno,expected", [
("1020", False),
("2010", False),
("2800", False),
("3200", True),
("6000", True),
("9100", True),
("", False),
("ABC", False),
])
def test_classification(self, accno, expected):
assert _isIncomeStatementAccount(accno) == expected
class TestFormatLastDayOfMonth:
def test_january(self):
assert _formatLastDayOfMonth(2025, 1) == "2025-01-31"
def test_february_nonLeap(self):
assert _formatLastDayOfMonth(2025, 2) == "2025-02-28"
def test_february_leap(self):
assert _formatLastDayOfMonth(2024, 2) == "2024-02-29"
def test_december(self):
assert _formatLastDayOfMonth(2025, 12) == "2025-12-31"
class TestRmaGetAccountBalances:
"""Reproduces the BuHa SoHa scenario: account 1020 closing balance per
31.12.2025 = 48'507.41, with prior-year opening 30'927.62.
"""
@pytest.mark.asyncio
async def test_buhaSohaScenario_yieldsAuthoritativeBalances(self):
connector = AccountingConnectorRma()
priorYearEndSaldo = 30927.62
decemberSaldo = 48507.41
# Simplified monthly progression: linear ramp from 31000 -> 48507.41
monthlySaldos = {
1: 31200.00, 2: 32500.00, 3: 33800.00, 4: 35200.00,
5: 36800.00, 6: 38500.00, 7: 40100.00, 8: 41900.00,
9: 43800.00, 10: 45500.00, 11: 47100.00, 12: decemberSaldo,
}
async def _fakeFetchRows(self, config, accno, fromDate, toDate):
if toDate == "2024-12-31":
return [("1020", priorYearEndSaldo)]
if toDate.startswith("2025-"):
month = int(toDate[5:7])
return [("1020", monthlySaldos[month])]
return []
async def _fakeChart(self, config, accountType=None):
return [type("AC", (), {"accountNumber": "1020"})()]
with patch.object(AccountingConnectorRma, "_fetchSaldoRows", _fakeFetchRows), \
patch.object(AccountingConnectorRma, "getChartOfAccounts", _fakeChart):
balances = await connector.getAccountBalances({"clientName": "test", "apiBaseUrl": "http://x", "apiKey": "k"}, years=[2025])
byPeriod = {(b.accountNumber, b.periodYear, b.periodMonth): b for b in balances}
annual = byPeriod[("1020", 2025, 0)]
assert annual.openingBalance == round(priorYearEndSaldo, 2)
assert annual.closingBalance == round(decemberSaldo, 2)
dec = byPeriod[("1020", 2025, 12)]
assert dec.closingBalance == round(decemberSaldo, 2)
assert dec.openingBalance == round(monthlySaldos[11], 2)
nov = byPeriod[("1020", 2025, 11)]
assert nov.closingBalance == round(monthlySaldos[11], 2)
jan = byPeriod[("1020", 2025, 1)]
assert jan.openingBalance == round(priorYearEndSaldo, 2)
assert jan.closingBalance == round(monthlySaldos[1], 2)
@pytest.mark.asyncio
async def test_incomeStatementAccountResetsToZero(self):
connector = AccountingConnectorRma()
async def _fakeFetchRows(self, config, accno, fromDate, toDate):
if toDate == "2024-12-31":
return [("6000", 99999.99)]
if toDate == "2025-01-31":
return [("6000", 5000.00)]
if toDate == "2025-12-31":
return [("6000", 60000.00)]
return []
async def _fakeChart(self, config, accountType=None):
return [type("AC", (), {"accountNumber": "6000"})()]
with patch.object(AccountingConnectorRma, "_fetchSaldoRows", _fakeFetchRows), \
patch.object(AccountingConnectorRma, "getChartOfAccounts", _fakeChart):
balances = await connector.getAccountBalances({"clientName": "x", "apiBaseUrl": "http://x", "apiKey": "k"}, years=[2025])
byPeriod = {(b.accountNumber, b.periodMonth): b for b in balances if b.periodYear == 2025}
# ER account January opening MUST be 0 (not 99999.99 from prior year)
assert byPeriod[("6000", 1)].openingBalance == 0.0
assert byPeriod[("6000", 0)].openingBalance == 0.0 # annual bucket too