"""Tests for data mapping functions (mappings.py).""" from unittest.mock import Mock, patch import pytest from cteward_ng.mappings import ( none_mapper, contract_mapper, contractlist_mapper, debit_mapper, debitlist_mapper, contributions_mapper, member_mapper, memo_mapper, memberlist_mapper, memberlist_to_ldapcsv_mapper, withdrawal_mapper, withdrawallist_mapper, ) # ── helpers ─────────────────────────────────────────────────────── def _ctx(data=None, config_base='', path='/test/', flags=None): req = Mock() req.path = path return { 'data': data or [], 'config': {'base': config_base}, 'request': req, 'flags': flags or [], } # ── none_mapper ───────────────────────────────────────────────── class TestNoneMapper: def test_identity(self): ctx = _ctx(data=[{'x': 1}]) result = none_mapper(ctx) assert result['data'] == [{'x': 1}] # ── contract mappers ───────────────────────────────────────────── class TestContractMapper: def test_single_contract(self): ctx = _ctx(data=[{ 'VertragNr': '42', 'ArtName': 'Ehrenmitglied', 'Sollstellung': 'monatlich', 'Betrag': 15.0, 'VertragBegin': '2020-01-01T00:00:00Z', 'VerwZw1': 'Mahnung', 'VerwZw2': '', }], config_base='https://api.example.com/') contract_mapper(ctx) assert ctx['data']['Vertragsnummer'] == 42 assert ctx['data']['Vertragsart'] == 'Ehrenmitglied' assert 'Mahnung' in ctx['data']['Verwendungszweck'] def test_empty_data(self): ctx = _ctx(data=[]) contract_mapper(ctx) assert ctx['data'] == {} class TestContractlistMapper: def test_list(self): ctx = _ctx(data=[{ 'VertragNr': '1', 'ArtName': 'A', 'Sollstellung': 'x', 'Betrag': 10, 'VertragBegin': '2020-01-01T00:00:00Z', }], config_base='https://api.example.com/') contractlist_mapper(ctx) assert ctx['data']['count'] == 1 assert ctx['data']['next'] is None # ── debit mappers ─────────────────────────────────────────────── class TestDebitMapper: def test_single_debit(self): ctx = _ctx(data=[{ 'VertragNr': '1', 'ArtName': 'Soll', 'Jahr': 2023, 'Monat': 6, 'Datum': '2023-06-01T00:00:00Z', 'Betrag': 25, 'Bezahlt': 25, 'Offen': 0, 'GUID': 'abc', }], config_base='https://api.example.com/', path='/debit/') debit_mapper(ctx) assert ctx['data']['Vertragsnummer'] == 1 assert ctx['data']['Betrag'] == 25 def test_zeroed_fields(self): ctx = _ctx(data=[{ 'VertragNr': '1', 'ArtName': 'Soll', 'Jahr': 2023, 'Monat': 6, 'Datum': '2023-06-01T00:00:00Z', 'Betrag': None, 'Bezahlt': None, 'Offen': None, }]) debit_mapper(ctx) assert ctx['data']['Betrag'] == 0 # ── contributions mapper ─────────────────────────────────────── class TestContributionsMapper: def test_aggregation(self): ctx = _ctx(data=[{ 'VertragNr': '1', 'ArtName': 'Ehren', 'Jahr': 2023, 'Betrag': 100, 'Bezahlt': 80, 'Offen': 20, }, { 'VertragNr': '1', 'ArtName': 'Ehren', 'Jahr': 2024, 'Betrag': 120, 'Bezahlt': 60, 'Offen': 60, }], config_base='https://api.example.com/') contributions_mapper(ctx) assert ctx['data']['total']['billed'] == 220 assert ctx['data']['total']['paid'] == 140 assert len(ctx['data']['contracts']) == 1 assert '2023' in ctx['data']['years'] assert '2024' in ctx['data']['years'] # ── member mappers ───────────────────────────────────────────── class TestMemberMapper: def test_full_member(self): ctx = _ctx(data=[{ 'Eintritt': '2020-01-15T00:00:00Z', 'Kontaktwoher': 'Alice, Bob', 'Vorname': 'Max', 'Nachname': 'Mustermann', 'Kurzname': 'maxm', 'Kennung3': 'crew', 'AdrNr': 42, 'MITGLNR': '1001', 'Zahlungsart': 'L', 'Betreuung': 'MÄNNLICH', }], config_base='https://api.example.com/', path='/member/maxm/', flags=['_board_']) member_mapper(ctx) d = ctx['data'] assert d['Vorname'] == 'Max' assert d['Email'] == 'maxm@c-base.org' assert d['Zahlungsart'] == 'Lastschrift' assert d['Geschlecht'] == 'M' assert d['Mitgliedsnummer'] == 1001 assert 'memo' in d # board-only def test_no_board_no_memo(self): ctx = _ctx(data=[{ 'Eintritt': '2020-01-01T00:00:00Z', 'Kontaktwoher': '', 'Kurzname': 'x', 'Kennung3': 'crew', }], flags=[]) member_mapper(ctx) assert 'memo' not in ctx['data'] class TestMemberlistMapper: def test_list(self): ctx = _ctx(data=[{ 'Kurzname': 'alice', 'AdrNr': 1, 'Kennung3': 'crew', 'Eintritt': '2020-01-01T00:00:00Z', 'Kontaktwoher': '', 'Vorname': 'A', 'Nachname': 'B', }]) memberlist_mapper(ctx) assert ctx['data']['count'] == 1 assert ctx['data']['results'][0]['Crewname'] == 'alice' def test_x_prefix_no_url(self): ctx = _ctx(data=[{ 'Kurzname': 'Xdisabled', 'AdrNr': 2, 'Kennung3': 'crew', 'Eintritt': '', 'Kontaktwoher': '', }]) memberlist_mapper(ctx) assert 'url' not in ctx['data']['results'][0] class TestMemberListToLdapCsv: def test_csv_shape(self): ctx = _ctx(data=[{ 'Kurzname': 'alice', 'Nachname': 'A', 'Vorname': 'B', 'Kennung3': 'crew', 'Eintritt': '2020-01-01T00:00:00Z', 'Telefon3': 'alice@mail.org', 'Kontaktwoher': 'Bob', }]) memberlist_to_ldapcsv_mapper(ctx) assert len(ctx['data']) == 1 row = ctx['data'][0] assert 'externe E-Mail' in row # ── memo mapper ──────────────────────────────────────────────── class TestMemoMapper: def test_basic(self): ctx = _ctx(data=[{ 'Eintritt': '2020-01-01T00:00:00Z', 'Memotext': '{\\rtf plain text}', 'Benutzer': 'admin', }]) memo_mapper(ctx) assert ctx['data']['Editor'] == 'admin' def test_no_data(self): ctx = _ctx(data=[]) memo_mapper(ctx) assert ctx['data'] == {} # ── withdrawal mappers ─────────────────────────────────────── class TestWithdrawalMapper: def test_single(self): ctx = _ctx(data=[{ 'VertragNr': '5', 'Adr_BLZ': '1234', 'Adr_Konto': '5678', 'IBAN': '', 'BIC': '', 'MandatsNr': 'MAND1', 'Betrag_EU': 50, 'Jahr': 2023, 'Zeitraum': 3, 'ErstDatum': '2023-03-15T00:00:00Z', }], config_base='https://api.example.com/', path='/withdrawal/') withdrawal_mapper(ctx) assert ctx['data']['Vertragsnummer'] == 5 assert ctx['data']['BLZ'] == '1234' def test_list(self): ctx = _ctx(data=[{ 'VertragNr': '1', 'Adr_BLZ': '', 'Adr_Konto': '', 'IBAN': '', 'BIC': '', 'MandatsNr': '', 'Betrag_EU': 0, 'Jahr': 2023, 'Zeitraum': 1, 'ErstDatum': '', }]) withdrawallist_mapper(ctx) assert ctx['data']['count'] == 1