diff --git a/dkb_robo/authentication.py b/dkb_robo/authentication.py index 3f7b368..bd96078 100644 --- a/dkb_robo/authentication.py +++ b/dkb_robo/authentication.py @@ -14,13 +14,8 @@ BASE_URL = 'https://banking.dkb.de/api' - - logger = logging.getLogger(__name__) -for hdlr in logger.handlers[:]: - print(hdlr) - class Authentication: """ Authentication class """ diff --git a/dkb_robo/dkb_robo.py b/dkb_robo/dkb_robo.py index 179c70a..7263dfe 100644 --- a/dkb_robo/dkb_robo.py +++ b/dkb_robo/dkb_robo.py @@ -139,14 +139,14 @@ def download(self, path: Path, download_all: bool, prepend_date: bool = False, m list_only = True postbox = PostBox(client=self.wrapper.client) documents = postbox.fetch_items() - if not download_all: # only unread documents documents = {id: item for id, item in documents.items() if item.message and item.message.read is False} accounts_by_id = {acc['id']: acc['account'] for acc in self.wrapper.account_dic.values()} - for doc in documents.values(): - self.download_doc(path=path, doc=doc, prepend_date=prepend_date, mark_read=mark_read, use_account_folders=use_account_folders, list_only=list_only, accounts_by_id=accounts_by_id) + if not list_only: + for doc in documents.values(): + self.download_doc(path=path, doc=doc, prepend_date=prepend_date, mark_read=mark_read, use_account_folders=use_account_folders, list_only=list_only, accounts_by_id=accounts_by_id) return documents diff --git a/dkb_robo/utilities.py b/dkb_robo/utilities.py index fa09c4e..0d47889 100644 --- a/dkb_robo/utilities.py +++ b/dkb_robo/utilities.py @@ -6,7 +6,7 @@ from string import digits, ascii_letters from typing import List, Tuple, Optional from datetime import datetime, timezone -from dataclasses import dataclass, fields, asdict +from dataclasses import dataclass, fields, asdict, is_dataclass import time import re @@ -68,14 +68,14 @@ def __post_init__(self): try: self.value = float(self.value) except Exception as err: - logger.error('Account.__post_init: conversion error: %s', err) + logger.error('Account.__post_init: value conversion error: %s', str(err)) self.value = None if self.conversionRate: try: self.conversionRate = float(self.conversionRate) except Exception as err: - logger.error('Account.__post_init: conversion error: %s', err) - self.value = None + logger.error('Account.__post_init: converstionRate conversion error: %s', str(err)) + self.conversionRate = None @filter_unexpected_fields @@ -92,7 +92,7 @@ def __post_init__(self): try: self.value = float(self.value) except Exception as err: - logger.error('Account.__post_init: conversion error: %s', err) + logger.error('PerformanceValue.__post_init: conversion error: %s', str(err)) self.value = None @@ -160,10 +160,8 @@ def object2dictionary(obj, key_lc=False, skip_list=None): for k, v in asdict(obj).items(): if isinstance(skip_list, list) and k in skip_list: continue - if isinstance(v, dict): + if is_dataclass(v): output_dict[k] = object2dictionary(v, key_lc=key_lc) - elif isinstance(v, list): - output_dict[k] = [object2dictionary(i, key_lc=key_lc) for i in v] else: if key_lc: output_dict[k.lower()] = v diff --git a/test/test_dkb_robo.py b/test/test_dkb_robo.py index 4cc2c92..3e43fc7 100644 --- a/test/test_dkb_robo.py +++ b/test/test_dkb_robo.py @@ -4,6 +4,7 @@ import sys import os import unittest +from pathlib import Path from unittest.mock import patch, MagicMock, Mock, mock_open from bs4 import BeautifulSoup from mechanicalsoup import LinkNotFoundError @@ -198,6 +199,123 @@ def test_015_scan_postbox(self): self.dkb.download.return_value = {'foo': 'bar'} self.assertEqual({'foo': 'bar'}, self.dkb.scan_postbox()) + @patch('dkb_robo.postbox.PostBox.fetch_items') + @patch('dkb_robo.dkb_robo.DKBRobo.download_doc', autospec=True) + def test_016_download(self, mock_download_doc, mock_fetch_items): + """ download_all_documents""" + path = Path('/some/path') + self.dkb.wrapper = Mock() + self.dkb.wrapper.client = Mock() + self.dkb.wrapper.account_dic = {'id1': {'id': 'id1', 'account': 'account1', 'read': False, 'foo': 'bar', 'iban': 'iban'}, 'id2': {'id': 'id2', 'account': 'account2', 'read': True, 'foo': 'bar'}} + mock_fetch_items.return_value = { + 'doc1': 'document1', + 'doc2': 'document2' + } + + documents = self.dkb.download(path=path, download_all=True) + self.assertEqual({'doc1': 'document1', 'doc2': 'document2'}, documents) + mock_download_doc.assert_any_call(self.dkb, path=path, doc=documents['doc1'], prepend_date=False, mark_read=True, use_account_folders=False, list_only=False, accounts_by_id={'id1': 'account1', 'id2': 'account2'}) + mock_download_doc.assert_any_call(self.dkb, path=path, doc=documents['doc2'], prepend_date=False, mark_read=True, use_account_folders=False, list_only=False, accounts_by_id={'id1': 'account1', 'id2': 'account2'}) + + @patch('dkb_robo.postbox.PostBox.fetch_items') + @patch('dkb_robo.dkb_robo.DKBRobo.download_doc', autospec=True) + def test_017_download(self, mock_download_doc, mock_fetch_items): + """ download_all_documents""" + path = Path('/some/path') + self.dkb.wrapper = Mock() + self.dkb.wrapper.client = Mock() + self.dkb.wrapper.account_dic = {'id1': {'id': 'id1', 'account': 'account1', 'read': False, 'foo': 'bar', 'iban': 'iban'}, 'id2': {'id': 'id2', 'account': 'account2', 'read': True, 'foo': 'bar'}} + unread_doc = MagicMock() + unread_doc.message.read = False + read_doc = MagicMock() + read_doc.message.read = True + mock_fetch_items.return_value = { + 'doc1': unread_doc, + 'doc2': read_doc + } + documents = self.dkb.download(path=path, download_all=False) + self.assertEqual(1, len(documents)) + mock_download_doc.assert_any_call(self.dkb, path=path, doc=documents['doc1'], prepend_date=False, mark_read=True, use_account_folders=False, list_only=False, accounts_by_id={'id1': 'account1', 'id2': 'account2'}) + + @patch('dkb_robo.postbox.PostBox.fetch_items') + @patch('dkb_robo.dkb_robo.DKBRobo.download_doc', autospec=True) + def test_018_download(self, mock_download_doc, mock_fetch_items): + """ download_all_documents""" + path = Path('/some/path') + self.dkb.wrapper = Mock() + self.dkb.wrapper.client = Mock() + self.dkb.wrapper.account_dic = {'id1': {'id': 'id1', 'account': 'account1', 'read': False, 'foo': 'bar', 'iban': 'iban'}, 'id2': {'id': 'id2', 'account': 'account2', 'read': True, 'foo': 'bar'}} + mock_fetch_items.return_value = { + 'doc1': 'document1', + 'doc2': 'document2' + } + + documents = self.dkb.download(path=None, download_all=True) + self.assertEqual({'doc1': 'document1', 'doc2': 'document2'}, documents) + mock_download_doc.assert_not_called() + + @patch('dkb_robo.dkb_robo.time.sleep', autospec=True) + def test_019_download_doc(self, mock_sleep): + """ download a single document """ + path = Path('/some/path') + doc = MagicMock() + doc.category.return_value = 'category' + doc.account.return_value = 'account' + doc.date.return_value = '2022-01-01' + doc.filename.return_value = 'document.pdf' + doc.subject.return_value = 'Document Subject' + doc.download.return_value = True + self.dkb.wrapper = MagicMock() + with self.assertLogs('dkb_robo', level='INFO') as lcm: + self.dkb.download_doc(path=path, doc=doc, prepend_date=True, mark_read=True, use_account_folders=True, list_only=False, accounts_by_id={}) + self.assertIn('INFO:dkb_robo:Downloading Document Subject to \\some\\path\\category\\account...', lcm.output) + + target = path / 'category' / 'account' + filename = '2022-01-01_document.pdf' + doc.download.assert_called_with(self.dkb.wrapper.client, target / filename) + doc.mark_read.assert_called_with(self.dkb.wrapper.client, True) + mock_sleep.assert_called_once_with(0.5) + + def test_020_download_doc(self): + """ list only """ + path = Path('/some/path') + doc = MagicMock() + doc.category.return_value = 'category' + doc.account.return_value = 'account' + doc.date.return_value = '2022-01-01' + doc.filename.return_value = 'document.pdf' + doc.subject.return_value = 'Document Subject' + doc.download.return_value = True + self.dkb.logger = MagicMock() + self.dkb.wrapper = MagicMock() + self.dkb.download_doc(path=path, doc=doc, prepend_date=True, mark_read=True, use_account_folders=True, list_only=True, accounts_by_id={}) + self.dkb.logger.info.assert_not_called() + doc.download.assert_not_called() + doc.mark_read.assert_not_called() + + @patch('dkb_robo.dkb_robo.time.sleep', autospec=True) + def test_021_download_doc(self, mock_sleep): + """ test existing file """ + path = Path('/some/path') + doc = MagicMock() + doc.category.return_value = 'category' + doc.account.return_value = 'account' + doc.date.return_value = '2022-01-01' + doc.filename.return_value = 'document.pdf' + doc.subject.return_value = 'Document Subject' + doc.download.return_value = False + self.dkb.logger = MagicMock() + self.dkb.wrapper = MagicMock() + self.dkb.download_doc(path=path, doc=doc, prepend_date=True, mark_read=True, use_account_folders=True, list_only=False, accounts_by_id={}) + target = path / 'category' / 'account' + filename = '2022-01-01_document.pdf' + self.dkb.logger.info.assert_called_with("File already exists. Skipping %s.", filename) + doc.download.assert_called_with(self.dkb.wrapper.client, target / filename) + doc.mark_read.assert_not_called() + mock_sleep.assert_not_called() + + + if __name__ == '__main__': unittest.main() diff --git a/test/test_exemptionorder.py b/test/test_exemptionorder.py index 908bf3e..aa3465e 100644 --- a/test/test_exemptionorder.py +++ b/test/test_exemptionorder.py @@ -109,7 +109,7 @@ def test_005__filter(self): result = [{'amount': None, 'used': None, 'currencycode': 'EUR', 'validfrom': '2020-01-01', 'validto': '9999-12-31', 'receivedat': '2020-01-01', 'type': 'joint', 'partner': {'dateofbirth': '1970-01-01', 'firstname': 'Jane', 'lastname': 'Doe', 'salutation': 'Frau', 'taxid': '1234567890'}}] with self.assertLogs('dkb_robo', level='INFO') as lcm: self.assertEqual(result, self.exo._filter(full_list)) - self.assertIn("ERROR:dkb_robo.utilities:Account.__post_init: conversion error: could not convert string to float: 'aa'", lcm.output) + self.assertIn("ERROR:dkb_robo.utilities:Account.__post_init: value conversion error: could not convert string to float: 'aa'", lcm.output) def test_006__filter(self): """ test StandingOrder._filter() with list """ diff --git a/test/test_standingorder.py b/test/test_standingorder.py index 023a785..ea5ae9c 100644 --- a/test/test_standingorder.py +++ b/test/test_standingorder.py @@ -134,7 +134,7 @@ def test_007__filter(self): result = [{'amount': None, 'currencycode': None, 'purpose': 'description', 'recipient': 'cardname', 'creditoraccount': {'iban': 'crediban', 'bic': 'credbic'}, 'interval': {'from': '2020-01-01', 'until': '2025-12-01', 'frequency': 'monthly', 'nextExecutionAt': '2020-02-01', 'holidayExecutionStrategy': None}}] with self.assertLogs('dkb_robo', level='INFO') as lcm: self.assertEqual(result, self.dkb._filter(full_list)) - self.assertIn("ERROR:dkb_robo.utilities:Account.__post_init: conversion error: could not convert string to float: 'aa'", lcm.output) + self.assertIn("ERROR:dkb_robo.utilities:Account.__post_init: value conversion error: could not convert string to float: 'aa'", lcm.output) def test_008__filter(self): """ test StandingOrders._filter() with incomplete list/conversion error """ diff --git a/test/test_utilities.py b/test/test_utilities.py index c8485ed..4bfc19d 100644 --- a/test/test_utilities.py +++ b/test/test_utilities.py @@ -5,10 +5,17 @@ import os import unittest import logging -from unittest.mock import patch +from unittest.mock import patch, Mock, MagicMock +from dataclasses import dataclass, asdict sys.path.insert(0, '.') sys.path.insert(0, '..') +@dataclass +class DataclassObject: + Attr1: str + attr2: int + attr3: dict + attr4: list class TestDKBRobo(unittest.TestCase): """ test class """ @@ -17,7 +24,7 @@ class TestDKBRobo(unittest.TestCase): def setUp(self): self.dir_path = os.path.dirname(os.path.realpath(__file__)) - from dkb_robo.utilities import validate_dates, generate_random_string, logger_setup, string2float, _convert_date_format, get_dateformat, get_valid_filename + from dkb_robo.utilities import validate_dates, generate_random_string, logger_setup, string2float, _convert_date_format, get_dateformat, get_valid_filename, object2dictionary, logger_setup self.validate_dates = validate_dates self.string2float = string2float self.generate_random_string = generate_random_string @@ -25,7 +32,9 @@ def setUp(self): self._convert_date_format = _convert_date_format self.get_dateformat = get_dateformat self.get_valid_filename = get_valid_filename + self.object2dictionary = object2dictionary self.logger = logging.getLogger('dkb_robo') + self.logger_setup = logger_setup @patch('time.time') def test_001_validate_dates(self, mock_time): @@ -261,6 +270,162 @@ def test_046_get_valid_filename(self, mock_rand): mock_rand.return_value = 'random' self.assertEqual('random.pdf', self.get_valid_filename(filename)) + + def test_047_object2dictionary(self): + """ test object2dictionary """ + + nested_obj = DataclassObject( + Attr1='nested_value1', + attr2=3, + attr3='nested_value1', + attr4='nested_value2' + ) + + test_obj = DataclassObject( + Attr1='value1', + attr2=2, + attr3=nested_obj, + attr4='foo' + ) + + expected_output = { + 'Attr1': 'value1', + 'attr2': 2, + 'attr3': {'Attr1': 'nested_value1', 'attr2': 3, 'attr3': 'nested_value1', 'attr4': 'nested_value2'}, + 'attr4': 'foo' + } + + result = self.object2dictionary(test_obj) + self.assertEqual(result, expected_output) + + + def test_048_object2dictionary(self): + """ test object2dictionary """ + test_obj = DataclassObject( + Attr1='value1', + attr2=2, + attr3='attr3', + attr4='attr4' + ) + expected_output = { + 'attr1': 'value1', + 'attr3': 'attr3', + 'attr4': 'attr4' + } + result = self.object2dictionary(test_obj, key_lc=True, skip_list=['attr2']) + self.assertEqual(result, expected_output) + + def test_049_logger_setup(self): + """ logger setup """ + self.assertTrue(self.logger_setup(False)) + + def test_050_logger_setup(self): + """ logger setup """ + self.assertTrue(self.logger_setup(True)) + + +class TestAmount(unittest.TestCase): + """ test class """ + def setUp(self): + self.dir_path = os.path.dirname(os.path.realpath(__file__)) + from dkb_robo.utilities import Amount + self.Amount = Amount + + @patch('dkb_robo.utilities.logger', autospec=True) + def test_047_amount(self, mock_logger): + """ test Amount """ + amount_data = { + 'value': '1000', + 'currencyCode': 'USD', + 'conversionRate': '1.2', + 'date': '2022-01-01', + 'unit': 'unit' + } + amount = self.Amount(**amount_data) + self.assertEqual(amount.value, 1000.0) + self.assertEqual(amount.currencyCode, 'USD') + self.assertEqual(amount.conversionRate, 1.2) + self.assertEqual(amount.date, '2022-01-01') + self.assertEqual(amount.unit, 'unit') + mock_logger.error.assert_not_called() + + @patch('dkb_robo.utilities.logger', autospec=True) + def test_049_amount(self, mock_logger): + """ test Amount with wrong value """ + amount_data = { + 'value': 'invalid', + 'currencyCode': 'USD', + 'conversionRate': '1.2', + 'date': '2022-01-01', + 'unit': 'unit' + } + amount = self.Amount(**amount_data) + self.assertFalse(amount.value) + self.assertEqual(amount.currencyCode, 'USD') + self.assertEqual(amount.conversionRate, 1.2) + self.assertEqual(amount.date, '2022-01-01') + self.assertEqual(amount.unit, 'unit') + mock_logger.error.assert_called_with('Account.__post_init: value conversion error: %s', "could not convert string to float: 'invalid'") + + + @patch('dkb_robo.utilities.logger', autospec=True) + def test_050_amount(self, mock_logger): + """ test Amount with wrong conversation rate """ + amount_data = { + 'value': '1000', + 'currencyCode': 'USD', + 'conversionRate': 'invalid', + 'date': '2022-01-01', + 'unit': 'unit' + } + amount = self.Amount(**amount_data) + self.assertEqual(amount.value, 1000) + self.assertEqual(amount.currencyCode, 'USD') + self.assertFalse(amount.conversionRate) + self.assertEqual(amount.date, '2022-01-01') + self.assertEqual(amount.unit, 'unit') + mock_logger.error.assert_called_with('Account.__post_init: converstionRate conversion error: %s', "could not convert string to float: 'invalid'") + + +class TestPerformanceValue(unittest.TestCase): + def setUp(self): + self.dir_path = os.path.dirname(os.path.realpath(__file__)) + from dkb_robo.utilities import PerformanceValue + self.PerformanceValue = PerformanceValue + + + @patch('dkb_robo.utilities.logger', autospec=True) + def test_051_performancevalue(self, mock_logger): + performance_value_data = { + 'currencyCode': 'USD', + 'value': '1000', + 'unit': 'unit' + } + + performance_value = self.PerformanceValue(**performance_value_data) + + self.assertEqual(performance_value.currencyCode, 'USD') + self.assertEqual(performance_value.value, 1000.0) + self.assertEqual(performance_value.unit, 'unit') + mock_logger.error.assert_not_called() + + @patch('dkb_robo.utilities.logger', autospec=True) + def test_052_performancevalue(self, mock_logger): + performance_value_data = { + 'currencyCode': 'USD', + 'value': 'invalid', + 'unit': 'unit' + } + + performance_value = self.PerformanceValue(**performance_value_data) + + self.assertEqual(performance_value.currencyCode, 'USD') + self.assertIsNone(performance_value.value) + self.assertEqual(performance_value.unit, 'unit') + mock_logger.error.assert_called_with('PerformanceValue.__post_init: conversion error: %s', "could not convert string to float: 'invalid'") + + + if __name__ == '__main__': unittest.main()