Go stringified sheet cells with fmt.Sprint, so a numeric VS read with UNFORMATTED_VALUE was rendered as 2.609767878e+09. Text columns of the payments sheet now go through sheets.CellString, which never uses exponent form; bank inference uses the same helper. Both sync implementations now append VS as a text cell so Sheets keeps it a string and leading zeros survive. Existing rows are not rewritten. Python already read VS as a digit string; a regression test pins that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
3.5 KiB
Python
91 lines
3.5 KiB
Python
import unittest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from scripts.match_payments import canonical_member_key, fetch_sheet_data, reconcile
|
|
|
|
|
|
class TestCanonicalMemberKey(unittest.TestCase):
|
|
def test_diacritics_and_case_collapse(self):
|
|
self.assertEqual(canonical_member_key("Mária Maco"), "maria maco")
|
|
self.assertEqual(canonical_member_key("MARIA MACO"), "maria maco")
|
|
self.assertEqual(canonical_member_key("maria maco"), "maria maco")
|
|
|
|
def test_whitespace_runs_collapse(self):
|
|
self.assertEqual(canonical_member_key("Mária Maco"), "maria maco")
|
|
self.assertEqual(canonical_member_key(" Mária Maco "), "maria maco")
|
|
|
|
def test_unknown_name_passes_through_normalized(self):
|
|
# Two genuinely different names must not collide.
|
|
self.assertNotEqual(
|
|
canonical_member_key("Mária Maco"),
|
|
canonical_member_key("Marek Maco"),
|
|
)
|
|
|
|
|
|
class TestReconcileTolerantPersonMatching(unittest.TestCase):
|
|
def _members(self):
|
|
return [("Mária Maco", "A", {"2026-04": (750, 4)})]
|
|
|
|
def _tx(self, person):
|
|
return {
|
|
"date": "2026-04-15",
|
|
"amount": 750,
|
|
"person": person,
|
|
"purpose": "2026-04",
|
|
"inferred_amount": 750,
|
|
"sender": "Maco Family",
|
|
"message": "fee",
|
|
}
|
|
|
|
def test_person_without_diacritics_matches(self):
|
|
result = reconcile(self._members(), ["2026-04"], [self._tx("Maria Maco")], {})
|
|
|
|
member = result["members"]["Mária Maco"]
|
|
self.assertEqual(member["months"]["2026-04"]["paid"], 750)
|
|
self.assertEqual(len(member["months"]["2026-04"]["transactions"]), 1)
|
|
self.assertEqual(result["unmatched"], [])
|
|
|
|
def test_person_with_extra_whitespace_matches(self):
|
|
result = reconcile(self._members(), ["2026-04"], [self._tx("Mária Maco")], {})
|
|
|
|
self.assertEqual(result["members"]["Mária Maco"]["months"]["2026-04"]["paid"], 750)
|
|
self.assertEqual(result["unmatched"], [])
|
|
|
|
def test_person_lowercase_matches(self):
|
|
result = reconcile(self._members(), ["2026-04"], [self._tx("mária maco")], {})
|
|
|
|
self.assertEqual(result["members"]["Mária Maco"]["months"]["2026-04"]["paid"], 750)
|
|
self.assertEqual(result["unmatched"], [])
|
|
|
|
def test_truly_unknown_person_still_unmatched(self):
|
|
result = reconcile(
|
|
self._members(), ["2026-04"], [self._tx("Někdo Neznámý")], {}
|
|
)
|
|
|
|
self.assertEqual(result["members"]["Mária Maco"]["months"]["2026-04"]["paid"], 0)
|
|
self.assertEqual(len(result["unmatched"]), 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|
|
|
|
|
|
class TestFetchSheetDataVS(unittest.TestCase):
|
|
"""Numeric VS cells arrive as floats (UNFORMATTED_VALUE) and must stay plain digit strings."""
|
|
|
|
def _fetch(self, vs_cell):
|
|
service = MagicMock()
|
|
service.spreadsheets().values().get().execute.return_value = {"values": [
|
|
["Date", "Amount", "manual fix", "Person", "Purpose", "Inferred Amount",
|
|
"Sender", "VS", "Message", "Bank ID", "Sync ID"],
|
|
["2026-04-15", 750, "", "", "", "", "Test Sender", vs_cell, "fee", "", "abc"],
|
|
]}
|
|
with patch("scripts.match_payments.get_sheets_service", return_value=service):
|
|
return fetch_sheet_data("sheet-id", "creds.json")
|
|
|
|
def test_numeric_vs_is_plain_digit_string(self):
|
|
self.assertEqual(self._fetch(2609767878.0)[0]["vs"], "2609767878")
|
|
|
|
def test_string_vs_is_kept(self):
|
|
self.assertEqual(self._fetch("0308")[0]["vs"], "0308")
|