diff --git a/src/invoice.py b/src/invoice.py index 271c3d6..e40da07 100755 --- a/src/invoice.py +++ b/src/invoice.py @@ -11,6 +11,7 @@ import json import os import sys +import logging from datetime import datetime # Reportlab is required for PDF generation. If it's missing, emit a clear @@ -38,8 +39,13 @@ def _load_profile(base_dir): try: with open(path, "r", encoding="utf-8") as fh: return json.load(fh) - except Exception: + except FileNotFoundError: return {} + except json.JSONDecodeError as exc: + logging.error("Failed to parse %s: %s", path, exc) + except OSError as exc: + logging.error("Unable to read %s: %s", path, exc) + return {} def _profile_sections(profile): diff --git a/test/check-application b/test/check-application index 2b64f2b..5d99d2c 100755 --- a/test/check-application +++ b/test/check-application @@ -11,3 +11,4 @@ PYTHONPATH=src python test/unit/billing_summary.test.py PYTHONPATH=src python test/unit/invoice_retrieval.test.py PYTHONPATH=src python test/unit/auth_boundaries.test.py PYTHONPATH=src python test/unit/accounts_listing.test.py +PYTHONPATH=src python test/unit/profile_loading.test.py diff --git a/test/unit/profile_loading.test.py b/test/unit/profile_loading.test.py new file mode 100644 index 0000000..18e0550 --- /dev/null +++ b/test/unit/profile_loading.test.py @@ -0,0 +1,33 @@ +import os +import tempfile +import unittest +from unittest import mock +import logging + +from invoice import _load_profile + + +class LoadProfileTests(unittest.TestCase): + def test_missing_file_returns_empty_profile(self): + with tempfile.TemporaryDirectory() as td: + self.assertEqual(_load_profile(td), {}) + + def test_invalid_json_logs_error(self): + with tempfile.TemporaryDirectory() as td: + path = os.path.join(td, "institution.json") + with open(path, "w", encoding="utf-8") as fh: + fh.write("{invalid") + with self.assertLogs(level="ERROR") as cm: + self.assertEqual(_load_profile(td), {}) + self.assertIn("Failed to parse", cm.output[0]) + + def test_os_error_logs_error(self): + with tempfile.TemporaryDirectory() as td: + with mock.patch("builtins.open", side_effect=PermissionError("denied")): + with self.assertLogs(level="ERROR") as cm: + self.assertEqual(_load_profile(td), {}) + self.assertIn("Unable to read", cm.output[0]) + + +if __name__ == "__main__": + unittest.main()