# 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 = ( "" "1020Bank48507.41" "2010AHV-1234.50" "
" ) 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