156 lines
5.7 KiB
Python
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
|