diff --git a/ofx_processor/processors/bpvf.py b/ofx_processor/processors/bpvf.py index 1372e3e..48867b9 100644 --- a/ofx_processor/processors/bpvf.py +++ b/ofx_processor/processors/bpvf.py @@ -3,6 +3,7 @@ import sys import click from ofxtools import OFXTree +from ofxtools.header import OFXHeaderError from ofx_processor.utils.processor import Processor, Line @@ -48,14 +49,14 @@ class BpvfProcessor(Processor): parser = OFXTree() try: parser.parse(self.filename) - except FileNotFoundError: - click.secho("Couldn't open ofx file", fg="red") + except (FileNotFoundError, OFXHeaderError): + click.secho("Couldn't open or parse ofx file", fg="red") sys.exit(1) ofx = parser.convert() if ofx is None: - click.secho("Couldn't parse ofx file", fg="red") + click.secho("Couldn't convert ofx file", fg="red") sys.exit(1) return ofx.statements[0].transactions diff --git a/ofx_processor/processors/revolut.py b/ofx_processor/processors/revolut.py index bed341f..505d3f4 100644 --- a/ofx_processor/processors/revolut.py +++ b/ofx_processor/processors/revolut.py @@ -1,4 +1,5 @@ import csv +import sys import click import dateparser @@ -52,9 +53,13 @@ class RevolutProcessor(Processor): account_name = "revolut" def parse_file(self): - with open(self.filename) as f: - reader = csv.DictReader(f, delimiter=";") - return [line for line in reader] + try: + with open(self.filename) as f: + reader = csv.DictReader(f, delimiter=";") + return [line for line in reader] + except FileNotFoundError: + click.secho("File not found", fg="red") + sys.exit(1) @staticmethod @click.command("revolut", help="Process Revolut bank statement (CSV)") diff --git a/ofx_processor/utils/processor.py b/ofx_processor/utils/processor.py index dd0caf7..c54f7f4 100644 --- a/ofx_processor/utils/processor.py +++ b/ofx_processor/utils/processor.py @@ -52,7 +52,7 @@ class Processor: self.iterable = self.parse_file() def parse_file(self): - return [] + return [] # pragma: nocover def push_to_ynab(self): transactions = self.get_transactions() diff --git a/tests/samples/bpvf.ofx b/tests/samples/bpvf.ofx new file mode 100644 index 0000000..76ae3e7 --- /dev/null +++ b/tests/samples/bpvf.ofx @@ -0,0 +1,206 @@ +OFXHEADER:100 +DATA:OFXSGML +VERSION:102 +SECURITY:NONE +ENCODING:USASCII +CHARSET:1252 +COMPRESSION:NONE +OLDFILEUID:NONE +NEWFILEUID:NONE + + + + +0 +INFO + +20200226060348 +FRA +20200226060348 +20200226060348 + + + + +12345678912 + +0 +INFO + + +EUR + +12345 +65432 +12345678912 +CHECKING + + +20200217 +20200226 + +DEBIT +20200226 +-9.66 +25364 HC4X +0719ATG +PRLV SEPA Company 3 +123456789 PAYPAL 542UHBON + + +DEBIT +20200225 +-2.40 +727278312 +2L9Z4OP +240220 CB****5555 +H.I.K 69VILLEURBANNE + + +DEBIT +20200225 +-39.20 +7834583486 +2LFVF8U +230220 CB****5555 +DELIVEROO FR WWW 39,20EUR 1 EURO = 1,000000 + + +DEBIT +20200225 +-9.99 +883783453 +0711ZB6 +PRLV SEPA Company 1 +Votre abonnement mobile: 06XXXXX 6498165189060897 + + +DEBIT +20200224 +-7.50 +651876109 +2JSCN09 +210220 CB****5555 +COMPANY FR LYON 6EME 7,50EUR 1 EURO = 1,000000 + + +DEBIT +20200224 +-34.99 +3483483 +0710J21 +PRLV SEPA Company 2 +24-02-2020 / 22-03-2020 56418710 + + +DEBIT +20200224 +-2.39 +79782112104 +5964661 +VIR Person 1 +481840871 Splitwise + + +CREDIT +20200220 ++235.00 +5408506055 +2D08DAF +VIREMENT Person 2 +Cadeau + + +CREDIT +20200220 ++55.00 +548910308 +2D088L5 +VIREMENT Company 3 +48716508719 + + +DEBIT +20200219 +-55.00 +54874910320 +2BCS20C +170220 CB****5555 +BDE INSA LYON 69VILLEURBANNE + + +DEBIT +20200219 +-0.90 +5478058 +2BINNA4 +180220 CB****5555 +GUY AND SONS FR LYON 0,90EUR 1 EURO = 1,000000 + + +DEBIT +20200219 +-1.40 +56098760894 +2BINNA3 +170220 CB****5555 +GUY AND SONS FR LYON 1,40EUR 1 EURO = 1,000000 + + +DEBIT +20200219 +-473.50 +5484916740 +5543368 +VIR Person 1 +65187460 Acompte cuisine 2 + + +DEBIT +20200218 +-96.96 +6518749560840 +070M39Z +PRLV SEPA Company 4 +487105874 Amazon.fr 3X QC.(OJBIYN:ZOFEUBZF51871 + + +DEBIT +20200217 +-232.00 +51981560870 +281U2GL +150220 CB****5555 +GRAND PARC PUY 85LES EPESSES + + +DEBIT +20200217 +-1.00 +6508941561987 +287L9X0 +140220 CB****5555 +UBER BV NL HELP.UBER.CO 1,00EUR 1 EURO = 1,000000 + + +CREDIT +20200217 ++8.60 +654897503260 +281M0PK +VIREMENT Person 5 +VIREMENT DE PERSON 6 + + + +1234.56 +20200226 + + +1234.56 +20200226 + + + + + diff --git a/tests/samples/bpvf_expected.json b/tests/samples/bpvf_expected.json new file mode 100644 index 0000000..eb09353 --- /dev/null +++ b/tests/samples/bpvf_expected.json @@ -0,0 +1 @@ +[{"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/bpvf_malformed.ofx b/tests/samples/bpvf_malformed.ofx new file mode 100644 index 0000000..e137782 --- /dev/null +++ b/tests/samples/bpvf_malformed.ofx @@ -0,0 +1,197 @@ +OFXHEADER:100 +DATA:OFXSGML +VERSION:102 +SECURITY:NONE +ENCODING:USASCII +CHARSET:1252 +COMPRESSION:NONE +OLDFILEUID:NONE +FRA +20200226060348 +20200226060348 + + + + +12345678912 + +0 +INFO + + +EUR + +12345 +65432 +12345678912 +CHECKING + + +20200217 +20200226 + +DEBIT +20200226 +-9.66 +25364 HC4X +0719ATG +PRLV SEPA Company 3 +123456789 PAYPAL 542UHBON + + +DEBIT +20200225 +-2.40 +727278312 +2L9Z4OP +240220 CB****5555 +H.I.K 69VILLEURBANNE + + +DEBIT +20200225 +-39.20 +7834583486 +2LFVF8U +230220 CB****5555 +DELIVEROO FR WWW 39,20EUR 1 EURO = 1,000000 + + +DEBIT +20200225 +-9.99 +883783453 +0711ZB6 +PRLV SEPA Company 1 +Votre abonnement mobile: 06XXXXX 6498165189060897 + + +DEBIT +20200224 +-7.50 +651876109 +2JSCN09 +210220 CB****5555 +COMPANY FR LYON 6EME 7,50EUR 1 EURO = 1,000000 + + +DEBIT +20200224 +-34.99 +3483483 +0710J21 +PRLV SEPA Company 2 +24-02-2020 / 22-03-2020 56418710 + + +DEBIT +20200224 +-2.39 +79782112104 +5964661 +VIR Person 1 +481840871 Splitwise + + +CREDIT +20200220 ++235.00 +5408506055 +2D08DAF +VIREMENT Person 2 +Cadeau + + +CREDIT +20200220 ++55.00 +548910308 +2D088L5 +VIREMENT Company 3 +48716508719 + + +DEBIT +20200219 +-55.00 +54874910320 +2BCS20C +170220 CB****5555 +BDE INSA LYON 69VILLEURBANNE + + +DEBIT +20200219 +-0.90 +5478058 +2BINNA4 +180220 CB****5555 +GUY AND SONS FR LYON 0,90EUR 1 EURO = 1,000000 + + +DEBIT +20200219 +-1.40 +56098760894 +2BINNA3 +170220 CB****5555 +GUY AND SONS FR LYON 1,40EUR 1 EURO = 1,000000 + + +DEBIT +20200219 +-473.50 +5484916740 +5543368 +VIR Person 1 +65187460 Acompte cuisine 2 + + +DEBIT +20200218 +-96.96 +6518749560840 +070M39Z +PRLV SEPA Company 4 +487105874 Amazon.fr 3X QC.(OJBIYN:ZOFEUBZF51871 + + +DEBIT +20200217 +-232.00 +51981560870 +281U2GL +150220 CB****5555 +GRAND PARC PUY 85LES EPESSES + + +DEBIT +20200217 +-1.00 +6508941561987 +287L9X0 +140220 CB****5555 +UBER BV NL HELP.UBER.CO 1,00EUR 1 EURO = 1,000000 + + +CREDIT +20200217 ++8.60 +654897503260 +281M0PK +VIREMENT Person 5 +VIREMENT DE PERSON 6 + + + +1234.56 +20200226 + + +1234.56 +20200226 + + + + + diff --git a/tests/samples/ce.ofx b/tests/samples/ce.ofx new file mode 100644 index 0000000..d5d41c4 --- /dev/null +++ b/tests/samples/ce.ofx @@ -0,0 +1,97 @@ +OFXHEADER:100 +DATA:OFXSGML +VERSION:102 +SECURITY:NONE +ENCODING:USASCII +CHARSET:1252 +COMPRESSION:NONE +OLDFILEUID:NONE +NEWFILEUID:NONE + + + + +0 +INFO + +20200226 +FRA +20200226 +20200226 + + + + +00 + +0 +INFO + + +EUR + +12345 +54321 +12345678932 +SAVINGS + + +20200223 +20200225 + +DEBIT +20200225 +-21,00 +54875116484966-41.57.21.325698 +CB DECATHLON FACT 240220 +CB DECATHLON FACT 240220 + + +DEBIT +20200225 +-7,00 +54875116484966-41.57.21.325698 +PRLV COMPANY +Company Ref Prlvt SEPA 99-1KIBHEF-01 45871984 + + +DEBIT +20200224 +-48,13 +54875116484966-41.57.21.325698 +CB 3403 MONOP FACT 210220 +CB 3403 MONOP FACT 210220 + + +DEBIT +20200224 +-1,20 +54875116484966-41.57.21.325698 +CB MALATIER FACT 210220 +CB MALATIER FACT 210220 + + +CREDIT +20200224 ++2,39 +54875116484966-41.57.21.325698 +VIR SEPA PERSON 1 +_ + + +CREDIT +20200224 ++14,49 +2402202020200224-05.41.39.551984 +VIR SEPA PERSON 2 +_ + + + +1234,56 +20200226 + + + + + \ No newline at end of file diff --git a/tests/samples/ce_expected.json b/tests/samples/ce_expected.json new file mode 100644 index 0000000..4e76833 --- /dev/null +++ b/tests/samples/ce_expected.json @@ -0,0 +1 @@ +[{"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/revolut.csv b/tests/samples/revolut.csv new file mode 100644 index 0000000..9207757 --- /dev/null +++ b/tests/samples/revolut.csv @@ -0,0 +1,10 @@ +Completed Date;Reference;Paid Out (EUR);Paid In (EUR);Exchange Out;Exchange In; Balance (EUR);Exchange Rate;Category +29 Jan;To Person 1;53,63;;;;21,66; ;Transfers +29 Jan;To Person 2;0,90;;;;75,29; ;Transfers +29 Jan;Refund from Company 2;;53,63;;;76,19; ;Shopping +24 Jan;To Person 3;8,50;;;;22,56; ;Transfers +16 Jan;To Person 4;1,40;;;;31,06; ;Transfers +10 Jan;To Person 5;2,01;;;;32,46; ;Transfers +10 Jan;To Person 6;1,21;;;;34,47; ;Transfers +5 Jan;Company 1;123,68;;USD 138,00;;35,68;FX-rate €1 = US$1,1158;Shopping +4 Jan;Top-up via Apple Pay;;100,00;;;159,36; ;General diff --git a/tests/samples/revolut_expected.json b/tests/samples/revolut_expected.json new file mode 100644 index 0000000..adc661d --- /dev/null +++ b/tests/samples/revolut_expected.json @@ -0,0 +1 @@ +[{"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/test_bpvf_processor.py b/tests/test_bpvf_processor.py index 3ea5561..888aecc 100644 --- a/tests/test_bpvf_processor.py +++ b/tests/test_bpvf_processor.py @@ -1,7 +1,8 @@ import datetime +import json import unittest -from ofx_processor.processors.bpvf import BpvfLine +from ofx_processor.processors.bpvf import BpvfLine, BpvfProcessor class BpvfTransaction: @@ -22,7 +23,7 @@ class BpvfTransaction: self.name = name -class BpvfProcessorTestCase(unittest.TestCase): +class BpvfLineTestCase(unittest.TestCase): def test_process_name_and_memo_no_change(self): name = "business" memo = "2020-01-17" @@ -84,5 +85,22 @@ class BpvfProcessorTestCase(unittest.TestCase): self.assertEqual(result_amount, expected_amount) +class BpvfProcessorTestCase(unittest.TestCase): + def test_file_not_found(self): + with self.assertRaises(SystemExit): + BpvfProcessor("filenotfound.ofx").get_transactions() + + def test_file(self): + transactions = BpvfProcessor("tests/samples/bpvf.ofx").get_transactions() + with open("tests/samples/bpvf_expected.json") as f: + expected_transactions = json.load(f) + + self.assertListEqual(transactions, expected_transactions) + + def test_file_malformed(self): + with self.assertRaises(SystemExit): + BpvfProcessor("tests/samples/bpvf_malformed.ofx") + + if __name__ == "__main__": unittest.main() # pragma: nocover diff --git a/tests/test_ce_processor.py b/tests/test_ce_processor.py new file mode 100644 index 0000000..f2d6ba3 --- /dev/null +++ b/tests/test_ce_processor.py @@ -0,0 +1,21 @@ +import json +import unittest + +from ofx_processor.processors.ce import CeProcessor + + +class CeProcessorTestCase(unittest.TestCase): + def test_file_not_found(self): + with self.assertRaises(SystemExit): + CeProcessor("filenotfound.ofx").get_transactions() + + def test_file(self): + transactions = CeProcessor("tests/samples/ce.ofx").get_transactions() + with open("tests/samples/ce_expected.json") as f: + expected_transactions = json.load(f) + + self.assertListEqual(transactions, expected_transactions) + + +if __name__ == "__main__": + unittest.main() # pragma: nocover diff --git a/tests/test_revolut_processor.py b/tests/test_revolut_processor.py index 2e00110..b684664 100644 --- a/tests/test_revolut_processor.py +++ b/tests/test_revolut_processor.py @@ -1,9 +1,11 @@ import datetime +import json import unittest from ofx_processor.processors.revolut import ( _amount_str_to_float, RevolutLine, + RevolutProcessor, ) @@ -70,5 +72,18 @@ class RevolutProcessorTestCase(unittest.TestCase): self.assertEqual(RevolutLine(line).get_payee(), expected) +class BpvfProcessorTestCase(unittest.TestCase): + def test_file_not_found(self): + with self.assertRaises(SystemExit): + RevolutProcessor("notfound.csv").get_transactions() + + def test_file(self): + transactions = RevolutProcessor("tests/samples/revolut.csv").get_transactions() + with open("tests/samples/revolut_expected.json") as f: + expected_transactions = json.load(f) + + self.assertListEqual(transactions, expected_transactions) + + if __name__ == "__main__": unittest.main() # pragma: nocover