From 339b071137f0c428b6e531331d496af1a50a6ac2 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Sat, 29 Feb 2020 11:12:22 +0100 Subject: [PATCH 1/6] Fix code smells --- ofx_processor/main.py | 3 ++- ofx_processor/utils/ynab.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ofx_processor/main.py b/ofx_processor/main.py index b11d43b..d9821e4 100644 --- a/ofx_processor/main.py +++ b/ofx_processor/main.py @@ -9,7 +9,8 @@ CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) @click.group(context_settings=CONTEXT_SETTINGS) @click.version_option() def cli(): - pass + pass # This function acts only as a group to collect other subcommands + # As such, it intentionally has no body cli.add_command(ynab.config, name="config") diff --git a/ofx_processor/utils/ynab.py b/ofx_processor/utils/ynab.py index 6a53bdb..9c92dda 100644 --- a/ofx_processor/utils/ynab.py +++ b/ofx_processor/utils/ynab.py @@ -29,7 +29,8 @@ def get_config_file_name(): @click.group() def config(): - pass + pass # This function acts only as a group to collect other subcommands + # As such, it intentionally has no body @click.command() From 412d331446f14fbb7ee9865fd8f027144f18253d Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Sat, 29 Feb 2020 11:46:11 +0100 Subject: [PATCH 2/6] Increase test coverage --- ofx_processor/utils/ynab.py | 5 +- tests/samples/bpvf_expected.json | 122 +++++++++++++++++++- tests/samples/ce_expected.json | 45 +++++++- tests/samples/config.ini | 12 ++ tests/samples/revolut_expected.json | 66 ++++++++++- tests/samples/transactions.json | 76 ++++++++++++ tests/{test_utils.py => test_end_to_end.py} | 9 +- tests/test_revolut_processor.py | 11 ++ tests/test_ynab_integration.py | 34 ++++++ 9 files changed, 369 insertions(+), 11 deletions(-) create mode 100644 tests/samples/config.ini create mode 100644 tests/samples/transactions.json rename tests/{test_utils.py => test_end_to_end.py} (87%) create mode 100644 tests/test_ynab_integration.py diff --git a/ofx_processor/utils/ynab.py b/ofx_processor/utils/ynab.py index 9c92dda..73314dd 100644 --- a/ofx_processor/utils/ynab.py +++ b/ofx_processor/utils/ynab.py @@ -33,15 +33,12 @@ def config(): # As such, it intentionally has no body -@click.command() +@config.command("edit") def edit_config(): config_file = get_config_file_name() click.edit(filename=config_file) -config.add_command(edit_config, "edit") - - def push_transactions(transactions, account): if not transactions: click.secho("No transaction, nothing to do.", fg="yellow") diff --git a/tests/samples/bpvf_expected.json b/tests/samples/bpvf_expected.json index eb09353..01eb781 100644 --- a/tests/samples/bpvf_expected.json +++ b/tests/samples/bpvf_expected.json @@ -1 +1,121 @@ -[{"date": "2020-02-26", "amount": -9660, "payee_name": "PRLV SEPA Company 3", "memo": "123456789 PAYPAL 542UHBON", "import_id": "YNAB:-9660:2020-02-26:1"}, {"date": "2020-02-25", "amount": -2400, "payee_name": "H.I.K 69VILLEURBANNE", "memo": "240220 CB****5555", "import_id": "YNAB:-2400:2020-02-25:1"}, {"date": "2020-02-25", "amount": -39200, "payee_name": "DELIVEROO FR WWW", "memo": "230220 CB****5555 39,20EUR 1 EURO = 1,000000", "import_id": "YNAB:-39200:2020-02-25:1"}, {"date": "2020-02-25", "amount": -9990, "payee_name": "PRLV SEPA Company 1", "memo": "Votre abonnement mobile: 06XXXXX 6498165189060897", "import_id": "YNAB:-9990:2020-02-25:1"}, {"date": "2020-02-24", "amount": -7500, "payee_name": "COMPANY FR LYON 6EME", "memo": "210220 CB****5555 7,50EUR 1 EURO = 1,000000", "import_id": "YNAB:-7500:2020-02-24:1"}, {"date": "2020-02-24", "amount": -34990, "payee_name": "PRLV SEPA Company 2", "memo": "24-02-2020 / 22-03-2020 56418710", "import_id": "YNAB:-34990:2020-02-24:1"}, {"date": "2020-02-24", "amount": -2390, "payee_name": "VIR Person 1", "memo": "481840871 Splitwise", "import_id": "YNAB:-2390:2020-02-24:1"}, {"date": "2020-02-20", "amount": 235000, "payee_name": "VIREMENT Person 2", "memo": "Cadeau", "import_id": "YNAB:235000:2020-02-20:1"}, {"date": "2020-02-20", "amount": 55000, "payee_name": "VIREMENT Company 3", "memo": "48716508719", "import_id": "YNAB:55000:2020-02-20:1"}, {"date": "2020-02-19", "amount": -55000, "payee_name": "BDE INSA LYON 69VILLEURBANNE", "memo": "170220 CB****5555", "import_id": "YNAB:-55000:2020-02-19:1"}, {"date": "2020-02-19", "amount": -900, "payee_name": "GUY AND SONS FR LYON", "memo": "180220 CB****5555 0,90EUR 1 EURO = 1,000000", "import_id": "YNAB:-900:2020-02-19:1"}, {"date": "2020-02-19", "amount": -1400, "payee_name": "GUY AND SONS FR LYON", "memo": "170220 CB****5555 1,40EUR 1 EURO = 1,000000", "import_id": "YNAB:-1400:2020-02-19:1"}, {"date": "2020-02-19", "amount": -473500, "payee_name": "VIR Person 1", "memo": "65187460 Acompte cuisine 2", "import_id": "YNAB:-473500:2020-02-19:1"}, {"date": "2020-02-18", "amount": -96960, "payee_name": "PRLV SEPA Company 4", "memo": "487105874 Amazon.fr 3X QC.(OJBIYN:ZOFEUBZF51871", "import_id": "YNAB:-96960:2020-02-18:1"}, {"date": "2020-02-17", "amount": -232000, "payee_name": "GRAND PARC PUY 85LES EPESSES", "memo": "150220 CB****5555", "import_id": "YNAB:-232000:2020-02-17:1"}, {"date": "2020-02-17", "amount": -1000, "payee_name": "UBER BV NL HELP.UBER.CO", "memo": "140220 CB****5555 1,00EUR 1 EURO = 1,000000", "import_id": "YNAB:-1000:2020-02-17:1"}, {"date": "2020-02-17", "amount": 8600, "payee_name": "VIREMENT Person 5", "memo": "VIREMENT DE PERSON 6", "import_id": "YNAB:8600:2020-02-17:1"}] \ No newline at end of file +[ + { + "date": "2020-02-26", + "amount": -9660, + "payee_name": "PRLV SEPA Company 3", + "memo": "123456789 PAYPAL 542UHBON", + "import_id": "YNAB:-9660:2020-02-26:1" + }, + { + "date": "2020-02-25", + "amount": -2400, + "payee_name": "H.I.K 69VILLEURBANNE", + "memo": "240220 CB****5555", + "import_id": "YNAB:-2400:2020-02-25:1" + }, + { + "date": "2020-02-25", + "amount": -39200, + "payee_name": "DELIVEROO FR WWW", + "memo": "230220 CB****5555 39,20EUR 1 EURO = 1,000000", + "import_id": "YNAB:-39200:2020-02-25:1" + }, + { + "date": "2020-02-25", + "amount": -9990, + "payee_name": "PRLV SEPA Company 1", + "memo": "Votre abonnement mobile: 06XXXXX 6498165189060897", + "import_id": "YNAB:-9990:2020-02-25:1" + }, + { + "date": "2020-02-24", + "amount": -7500, + "payee_name": "COMPANY FR LYON 6EME", + "memo": "210220 CB****5555 7,50EUR 1 EURO = 1,000000", + "import_id": "YNAB:-7500:2020-02-24:1" + }, + { + "date": "2020-02-24", + "amount": -34990, + "payee_name": "PRLV SEPA Company 2", + "memo": "24-02-2020 / 22-03-2020 56418710", + "import_id": "YNAB:-34990:2020-02-24:1" + }, + { + "date": "2020-02-24", + "amount": -2390, + "payee_name": "VIR Person 1", + "memo": "481840871 Splitwise", + "import_id": "YNAB:-2390:2020-02-24:1" + }, + { + "date": "2020-02-20", + "amount": 235000, + "payee_name": "VIREMENT Person 2", + "memo": "Cadeau", + "import_id": "YNAB:235000:2020-02-20:1" + }, + { + "date": "2020-02-20", + "amount": 55000, + "payee_name": "VIREMENT Company 3", + "memo": "48716508719", + "import_id": "YNAB:55000:2020-02-20:1" + }, + { + "date": "2020-02-19", + "amount": -55000, + "payee_name": "BDE INSA LYON 69VILLEURBANNE", + "memo": "170220 CB****5555", + "import_id": "YNAB:-55000:2020-02-19:1" + }, + { + "date": "2020-02-19", + "amount": -900, + "payee_name": "GUY AND SONS FR LYON", + "memo": "180220 CB****5555 0,90EUR 1 EURO = 1,000000", + "import_id": "YNAB:-900:2020-02-19:1" + }, + { + "date": "2020-02-19", + "amount": -1400, + "payee_name": "GUY AND SONS FR LYON", + "memo": "170220 CB****5555 1,40EUR 1 EURO = 1,000000", + "import_id": "YNAB:-1400:2020-02-19:1" + }, + { + "date": "2020-02-19", + "amount": -473500, + "payee_name": "VIR Person 1", + "memo": "65187460 Acompte cuisine 2", + "import_id": "YNAB:-473500:2020-02-19:1" + }, + { + "date": "2020-02-18", + "amount": -96960, + "payee_name": "PRLV SEPA Company 4", + "memo": "487105874 Amazon.fr 3X QC.(OJBIYN:ZOFEUBZF51871", + "import_id": "YNAB:-96960:2020-02-18:1" + }, + { + "date": "2020-02-17", + "amount": -232000, + "payee_name": "GRAND PARC PUY 85LES EPESSES", + "memo": "150220 CB****5555", + "import_id": "YNAB:-232000:2020-02-17:1" + }, + { + "date": "2020-02-17", + "amount": -1000, + "payee_name": "UBER BV NL HELP.UBER.CO", + "memo": "140220 CB****5555 1,00EUR 1 EURO = 1,000000", + "import_id": "YNAB:-1000:2020-02-17:1" + }, + { + "date": "2020-02-17", + "amount": 8600, + "payee_name": "VIREMENT Person 5", + "memo": "VIREMENT DE PERSON 6", + "import_id": "YNAB:8600:2020-02-17:1" + } +] \ No newline at end of file diff --git a/tests/samples/ce_expected.json b/tests/samples/ce_expected.json index 4e76833..28751ab 100644 --- a/tests/samples/ce_expected.json +++ b/tests/samples/ce_expected.json @@ -1 +1,44 @@ -[{"date": "2020-02-25", "amount": -21000, "payee_name": "CB DECATHLON", "memo": "FACT 240220", "import_id": "YNAB:-21000:2020-02-25:1"}, {"date": "2020-02-25", "amount": -7000, "payee_name": "PRLV COMPANY", "memo": "Company Ref Prlvt SEPA 99-1KIBHEF-01 45871984", "import_id": "YNAB:-7000:2020-02-25:1"}, {"date": "2020-02-24", "amount": -48130, "payee_name": "CB 3403 MONOP", "memo": "FACT 210220", "import_id": "YNAB:-48130:2020-02-24:1"}, {"date": "2020-02-24", "amount": -1200, "payee_name": "CB MALATIER", "memo": "FACT 210220", "import_id": "YNAB:-1200:2020-02-24:1"}, {"date": "2020-02-24", "amount": 2390, "payee_name": "VIR SEPA PERSON 1", "memo": "_", "import_id": "YNAB:2390:2020-02-24:1"}, {"date": "2020-02-24", "amount": 14490, "payee_name": "VIR SEPA PERSON 2", "memo": "_", "import_id": "YNAB:14490:2020-02-24:1"}] \ No newline at end of file +[ + { + "date": "2020-02-25", + "amount": -21000, + "payee_name": "CB DECATHLON", + "memo": "FACT 240220", + "import_id": "YNAB:-21000:2020-02-25:1" + }, + { + "date": "2020-02-25", + "amount": -7000, + "payee_name": "PRLV COMPANY", + "memo": "Company Ref Prlvt SEPA 99-1KIBHEF-01 45871984", + "import_id": "YNAB:-7000:2020-02-25:1" + }, + { + "date": "2020-02-24", + "amount": -48130, + "payee_name": "CB 3403 MONOP", + "memo": "FACT 210220", + "import_id": "YNAB:-48130:2020-02-24:1" + }, + { + "date": "2020-02-24", + "amount": -1200, + "payee_name": "CB MALATIER", + "memo": "FACT 210220", + "import_id": "YNAB:-1200:2020-02-24:1" + }, + { + "date": "2020-02-24", + "amount": 2390, + "payee_name": "VIR SEPA PERSON 1", + "memo": "_", + "import_id": "YNAB:2390:2020-02-24:1" + }, + { + "date": "2020-02-24", + "amount": 14490, + "payee_name": "VIR SEPA PERSON 2", + "memo": "_", + "import_id": "YNAB:14490:2020-02-24:1" + } +] \ No newline at end of file diff --git a/tests/samples/config.ini b/tests/samples/config.ini new file mode 100644 index 0000000..69847f3 --- /dev/null +++ b/tests/samples/config.ini @@ -0,0 +1,12 @@ +[DEFAULT] +token = +budget = + +[bpvf] +account = + +[revolut] +account = + +[ce] +account = diff --git a/tests/samples/revolut_expected.json b/tests/samples/revolut_expected.json index adc661d..c5e6709 100644 --- a/tests/samples/revolut_expected.json +++ b/tests/samples/revolut_expected.json @@ -1 +1,65 @@ -[{"date": "2020-01-29", "amount": -53630, "payee_name": "To Person 1", "memo": "Transfers", "import_id": "YNAB:-53630:2020-01-29:1"}, {"date": "2020-01-29", "amount": -900, "payee_name": "To Person 2", "memo": "Transfers", "import_id": "YNAB:-900:2020-01-29:1"}, {"date": "2020-01-29", "amount": 53630, "payee_name": "Refund from Company 2", "memo": "Shopping", "import_id": "YNAB:53630:2020-01-29:1"}, {"date": "2020-01-24", "amount": -8500, "payee_name": "To Person 3", "memo": "Transfers", "import_id": "YNAB:-8500:2020-01-24:1"}, {"date": "2020-01-16", "amount": -1400, "payee_name": "To Person 4", "memo": "Transfers", "import_id": "YNAB:-1400:2020-01-16:1"}, {"date": "2020-01-10", "amount": -2009, "payee_name": "To Person 5", "memo": "Transfers", "import_id": "YNAB:-2009:2020-01-10:1"}, {"date": "2020-01-10", "amount": -1210, "payee_name": "To Person 6", "memo": "Transfers", "import_id": "YNAB:-1210:2020-01-10:1"}, {"date": "2020-01-05", "amount": -123680, "payee_name": "Company 1", "memo": "Shopping - FX-rate \u20ac1\u2008=\u2008US$1,1158", "import_id": "YNAB:-123680:2020-01-05:1"}, {"date": "2020-01-04", "amount": 100000, "payee_name": "Top-up via Apple Pay", "memo": "General", "import_id": "YNAB:100000:2020-01-04:1"}] \ No newline at end of file +[ + { + "date": "2020-01-29", + "amount": -53630, + "payee_name": "To Person 1", + "memo": "Transfers", + "import_id": "YNAB:-53630:2020-01-29:1" + }, + { + "date": "2020-01-29", + "amount": -900, + "payee_name": "To Person 2", + "memo": "Transfers", + "import_id": "YNAB:-900:2020-01-29:1" + }, + { + "date": "2020-01-29", + "amount": 53630, + "payee_name": "Refund from Company 2", + "memo": "Shopping", + "import_id": "YNAB:53630:2020-01-29:1" + }, + { + "date": "2020-01-24", + "amount": -8500, + "payee_name": "To Person 3", + "memo": "Transfers", + "import_id": "YNAB:-8500:2020-01-24:1" + }, + { + "date": "2020-01-16", + "amount": -1400, + "payee_name": "To Person 4", + "memo": "Transfers", + "import_id": "YNAB:-1400:2020-01-16:1" + }, + { + "date": "2020-01-10", + "amount": -2009, + "payee_name": "To Person 5", + "memo": "Transfers", + "import_id": "YNAB:-2009:2020-01-10:1" + }, + { + "date": "2020-01-10", + "amount": -1210, + "payee_name": "To Person 6", + "memo": "Transfers", + "import_id": "YNAB:-1210:2020-01-10:1" + }, + { + "date": "2020-01-05", + "amount": -123680, + "payee_name": "Company 1", + "memo": "Shopping - FX-rate \u20ac1\u2008=\u2008US$1,1158", + "import_id": "YNAB:-123680:2020-01-05:1" + }, + { + "date": "2020-01-04", + "amount": 100000, + "payee_name": "Top-up via Apple Pay", + "memo": "General", + "import_id": "YNAB:100000:2020-01-04:1" + } +] \ No newline at end of file diff --git a/tests/samples/transactions.json b/tests/samples/transactions.json new file mode 100644 index 0000000..dab48a0 --- /dev/null +++ b/tests/samples/transactions.json @@ -0,0 +1,76 @@ +{ + "transactions": [ + { + "date": "2020-01-29", + "amount": -53630, + "payee_name": "To Person 1", + "memo": "Transfers", + "import_id": "YNAB:-53630:2020-01-29:1", + "account_id": "" + }, + { + "date": "2020-01-29", + "amount": -900, + "payee_name": "To Person 2", + "memo": "Transfers", + "import_id": "YNAB:-900:2020-01-29:1", + "account_id": "" + }, + { + "date": "2020-01-29", + "amount": 53630, + "payee_name": "Refund from Company 2", + "memo": "Shopping", + "import_id": "YNAB:53630:2020-01-29:1", + "account_id": "" + }, + { + "date": "2020-01-24", + "amount": -8500, + "payee_name": "To Person 3", + "memo": "Transfers", + "import_id": "YNAB:-8500:2020-01-24:1", + "account_id": "" + }, + { + "date": "2020-01-16", + "amount": -1400, + "payee_name": "To Person 4", + "memo": "Transfers", + "import_id": "YNAB:-1400:2020-01-16:1", + "account_id": "" + }, + { + "date": "2020-01-10", + "amount": -2009, + "payee_name": "To Person 5", + "memo": "Transfers", + "import_id": "YNAB:-2009:2020-01-10:1", + "account_id": "" + }, + { + "date": "2020-01-10", + "amount": -1210, + "payee_name": "To Person 6", + "memo": "Transfers", + "import_id": "YNAB:-1210:2020-01-10:1", + "account_id": "" + }, + { + "date": "2020-01-05", + "amount": -123680, + "payee_name": "Company 1", + "memo": "Shopping - FX-rate €1 = US$1,1158", + "import_id": "YNAB:-123680:2020-01-05:1", + "account_id": "" + }, + { + "date": "2020-01-04", + "amount": 100000, + "payee_name": "Top-up via Apple Pay", + "memo": "General", + "import_id": "YNAB:100000:2020-01-04:1", + "account_id": "" + } + ] +} \ No newline at end of file diff --git a/tests/test_utils.py b/tests/test_end_to_end.py similarity index 87% rename from tests/test_utils.py rename to tests/test_end_to_end.py index ceb9a37..ff664f7 100644 --- a/tests/test_utils.py +++ b/tests/test_end_to_end.py @@ -11,6 +11,11 @@ from ofx_processor.utils.ynab import config class UtilsTestCase(unittest.TestCase): + """ + This class needs to run before any other that imports + ofx_processor.main.cli because it tests import time stuff. + """ + @staticmethod def test_discover_processors(): ce_main = CeProcessor.main @@ -28,7 +33,3 @@ class UtilsTestCase(unittest.TestCase): call(config, name="config"), ] add_command.assert_has_calls(calls, any_order=True) - - -if __name__ == "__main__": - unittest.main() # pragma: nocover diff --git a/tests/test_revolut_processor.py b/tests/test_revolut_processor.py index 5760a7e..a3366a8 100644 --- a/tests/test_revolut_processor.py +++ b/tests/test_revolut_processor.py @@ -85,5 +85,16 @@ class RevolutProcessorTestCase(unittest.TestCase): self.assertListEqual(transactions, expected_transactions) +# class RevolutEndToEndTestCase(unittest.TestCase): +# def test_data_sent_to_ynab(self): +# with open("tests/samples/config.ini") as config_file: +# config_content = config_file.read() +# +# with mock.patch("__main__.open", mock.mock_open(read_data=config_content)) as m: +# pass +# print(config_content) +# self.assertTrue(False) + + if __name__ == "__main__": unittest.main() # pragma: nocover diff --git a/tests/test_ynab_integration.py b/tests/test_ynab_integration.py new file mode 100644 index 0000000..f2b69d1 --- /dev/null +++ b/tests/test_ynab_integration.py @@ -0,0 +1,34 @@ +import json +import unittest +from unittest import mock + +from ofx_processor.utils import ynab + + +class YNABIntegrationTestCase(unittest.TestCase): + @mock.patch("requests.post") + def test_data_sent_to_ynab(self, post): + ynab.DEFAULT_CONFIG_DIR = "tests/samples" + + with open("tests/samples/revolut_expected.json", encoding="utf-8") as f: + transactions = json.load(f) + with open("tests/samples/transactions.json", encoding="utf-8") as f: + expected_data = json.load(f) + + expected_headers = {"Authorization": f"Bearer "} + expected_url = f"{ynab.BASE_URL}/budgets//transactions" + + ynab.push_transactions(transactions, "revolut") + + post.assert_called_once_with( + expected_url, json=expected_data, headers=expected_headers + ) + + @mock.patch("requests.post") + def test_no_data_to_send(self, post): + ynab.push_transactions([], "") + post.assert_not_called() + + +if __name__ == "__main__": + unittest.main() # pragma: nocover From ce85620f7817fb076f274e0125cbc7f367827e25 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Sat, 29 Feb 2020 11:50:55 +0100 Subject: [PATCH 3/6] Test config edit --- tests/test_end_to_end.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_end_to_end.py b/tests/test_end_to_end.py index ff664f7..6ac102b 100644 --- a/tests/test_end_to_end.py +++ b/tests/test_end_to_end.py @@ -7,6 +7,7 @@ from click.testing import CliRunner from ofx_processor.processors.bpvf import BpvfProcessor from ofx_processor.processors.ce import CeProcessor from ofx_processor.processors.revolut import RevolutProcessor +from ofx_processor.utils import ynab from ofx_processor.utils.ynab import config @@ -33,3 +34,20 @@ class UtilsTestCase(unittest.TestCase): call(config, name="config"), ] add_command.assert_has_calls(calls, any_order=True) + + +class ConfigEditTestCase(unittest.TestCase): + @mock.patch("click.edit") + def test_config_edit(self, edit): + config_dir = "tests/samples" + ynab.DEFAULT_CONFIG_DIR = config_dir + expected_filename = f"{config_dir}/config.ini" + runner = CliRunner() + from ofx_processor.main import cli + + # This is run at import time and the cli module is already imported before this test + # so we need to re-run the add_command to make it available. + cli.add_command(ynab.config, name="config") + + runner.invoke(cli, ["config", "edit"]) + edit.assert_called_once_with(filename=expected_filename) From 3d4758c0d9de7d5bca46f2958d15efd6c98faa22 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Sat, 29 Feb 2020 11:53:43 +0100 Subject: [PATCH 4/6] Improve doc --- ofx_processor/main.py | 6 ++++-- ofx_processor/utils/ynab.py | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ofx_processor/main.py b/ofx_processor/main.py index d9821e4..99630db 100644 --- a/ofx_processor/main.py +++ b/ofx_processor/main.py @@ -9,8 +9,10 @@ CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) @click.group(context_settings=CONTEXT_SETTINGS) @click.version_option() def cli(): - pass # This function acts only as a group to collect other subcommands - # As such, it intentionally has no body + """ + Import your data to YNAB with the processors listed below + or manage your config. + """ cli.add_command(ynab.config, name="config") diff --git a/ofx_processor/utils/ynab.py b/ofx_processor/utils/ynab.py index 73314dd..01b5c48 100644 --- a/ofx_processor/utils/ynab.py +++ b/ofx_processor/utils/ynab.py @@ -29,8 +29,9 @@ def get_config_file_name(): @click.group() def config(): - pass # This function acts only as a group to collect other subcommands - # As such, it intentionally has no body + """ + Manage configuration with subcommands + """ @config.command("edit") From a54ac06b122e11862182c939f3bce79f5f1d965e Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Sat, 29 Feb 2020 12:27:39 +0100 Subject: [PATCH 5/6] Increase test coverage --- ofx_processor/utils/ynab.py | 2 +- tests/test_end_to_end.py | 131 ++++++++++++++++++++++++++++++++ tests/test_revolut_processor.py | 11 --- 3 files changed, 132 insertions(+), 12 deletions(-) diff --git a/ofx_processor/utils/ynab.py b/ofx_processor/utils/ynab.py index 01b5c48..641595a 100644 --- a/ofx_processor/utils/ynab.py +++ b/ofx_processor/utils/ynab.py @@ -72,7 +72,7 @@ def push_transactions(transactions, account): created = set() for transaction in data["transactions"]: - matched_id = transaction["matched_transaction_id"] + matched_id = transaction.get("matched_transaction_id") if not matched_id or matched_id not in created: created.add(transaction["id"]) diff --git a/tests/test_end_to_end.py b/tests/test_end_to_end.py index 6ac102b..eb27189 100644 --- a/tests/test_end_to_end.py +++ b/tests/test_end_to_end.py @@ -7,6 +7,7 @@ from click.testing import CliRunner from ofx_processor.processors.bpvf import BpvfProcessor from ofx_processor.processors.ce import CeProcessor from ofx_processor.processors.revolut import RevolutProcessor +from ofx_processor.utils import utils from ofx_processor.utils import ynab from ofx_processor.utils.ynab import config @@ -51,3 +52,133 @@ class ConfigEditTestCase(unittest.TestCase): runner.invoke(cli, ["config", "edit"]) edit.assert_called_once_with(filename=expected_filename) + + +class DataTestCase(unittest.TestCase): + @mock.patch("requests.post") + def test_revolut_sends_data_only_created(self, post): + ynab.DEFAULT_CONFIG_DIR = "tests/samples" + post.return_value.json.return_value = { + "data": { + "transactions": [ + { + "id": "ynab_existing:1", + "matched_transaction_id": "imported_matched:2", + }, + { + "id": "imported_matched:2", + "matched_transaction_id": "ynab_existing:1", + }, + { + "id": "imported_matched:3", + "matched_transaction_id": "ynab_existing:4", + }, + { + "id": "ynab_existing:4", + "matched_transaction_id": "imported_matched:3", + }, + {"id": "created:5", "matched_transaction_id": None}, + {"id": "created:6", "matched_transaction_id": None}, + {"id": "created:7", "matched_transaction_id": None}, + {"id": "created:8", "matched_transaction_id": None}, + {"id": "created:9", "matched_transaction_id": None}, + {"id": "created:10", "matched_transaction_id": None}, + {"id": "created:11", "matched_transaction_id": None}, + ], + "duplicate_import_ids": [], + } + } + from ofx_processor.main import cli + + # This is run at import time and the cli module is already imported before this test + # so we need to re-run the add_command to make it available. + utils.discover_processors(cli) + + runner = CliRunner() + result = runner.invoke(cli, ["revolut", "tests/samples/revolut.csv"]) + + self.assertEqual(result.exit_code, 0) + self.assertIn("Processed 9 transactions total.", result.output) + self.assertIn("9 transactions created in YNAB.", result.output) + self.assertNotIn("transactions ignored (duplicates).", result.output) + + @mock.patch("requests.post") + def test_revolut_sends_data_some_created_some_duplicates(self, post): + ynab.DEFAULT_CONFIG_DIR = "tests/samples" + post.return_value.json.return_value = { + "data": { + "transactions": [ + { + "id": "ynab_existing:1", + "matched_transaction_id": "imported_matched:2", + }, + { + "id": "imported_matched:2", + "matched_transaction_id": "ynab_existing:1", + }, + { + "id": "imported_matched:3", + "matched_transaction_id": "ynab_existing:4", + }, + { + "id": "ynab_existing:4", + "matched_transaction_id": "imported_matched:3", + }, + {"id": "created:5", "matched_transaction_id": None}, + ], + "duplicate_import_ids": [ + "duplicate:6", + "duplicate:7", + "duplicate:8", + "duplicate:9", + "duplicate:10", + "duplicate:11", + ], + } + } + from ofx_processor.main import cli + + # This is run at import time and the cli module is already imported before this test + # so we need to re-run the add_command to make it available. + utils.discover_processors(cli) + + runner = CliRunner() + result = runner.invoke(cli, ["revolut", "tests/samples/revolut.csv"]) + + self.assertEqual(result.exit_code, 0) + self.assertIn("Processed 9 transactions total.", result.output) + self.assertIn("3 transactions created in YNAB.", result.output) + self.assertIn("6 transactions ignored (duplicates).", result.output) + + @mock.patch("requests.post") + def test_revolut_sends_data_only_duplicates(self, post): + ynab.DEFAULT_CONFIG_DIR = "tests/samples" + post.return_value.json.return_value = { + "data": { + "transactions": [], + "duplicate_import_ids": [ + "duplicate:1", + "duplicate:2", + "duplicate:3", + "duplicate:4", + "duplicate:5", + "duplicate:6", + "duplicate:7", + "duplicate:8", + "duplicate:9", + ], + } + } + from ofx_processor.main import cli + + # This is run at import time and the cli module is already imported before this test + # so we need to re-run the add_command to make it available. + utils.discover_processors(cli) + + runner = CliRunner() + result = runner.invoke(cli, ["revolut", "tests/samples/revolut.csv"]) + + self.assertEqual(result.exit_code, 0) + self.assertIn("Processed 9 transactions total.", result.output) + self.assertNotIn("transactions created in YNAB.", result.output) + self.assertIn("9 transactions ignored (duplicates).", result.output) diff --git a/tests/test_revolut_processor.py b/tests/test_revolut_processor.py index a3366a8..5760a7e 100644 --- a/tests/test_revolut_processor.py +++ b/tests/test_revolut_processor.py @@ -85,16 +85,5 @@ class RevolutProcessorTestCase(unittest.TestCase): self.assertListEqual(transactions, expected_transactions) -# class RevolutEndToEndTestCase(unittest.TestCase): -# def test_data_sent_to_ynab(self): -# with open("tests/samples/config.ini") as config_file: -# config_content = config_file.read() -# -# with mock.patch("__main__.open", mock.mock_open(read_data=config_content)) as m: -# pass -# print(config_content) -# self.assertTrue(False) - - if __name__ == "__main__": unittest.main() # pragma: nocover From 3a45f0a510a2ecea4e5730d5a172ac2c1a119085 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Sat, 29 Feb 2020 12:29:15 +0100 Subject: [PATCH 6/6] Move transaction_ids dict to instance attribute --- ofx_processor/utils/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ofx_processor/utils/processor.py b/ofx_processor/utils/processor.py index c54f7f4..c0249ce 100644 --- a/ofx_processor/utils/processor.py +++ b/ofx_processor/utils/processor.py @@ -43,13 +43,13 @@ class Line: class Processor: - transaction_ids = defaultdict(int) line_class = Line account_name = None def __init__(self, filename): self.filename = filename self.iterable = self.parse_file() + self.transaction_ids = defaultdict(int) def parse_file(self): return [] # pragma: nocover