aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/unit/test_commands_account.py50
-rw-r--r--tests/unit/test_output.py84
2 files changed, 134 insertions, 0 deletions
diff --git a/tests/unit/test_commands_account.py b/tests/unit/test_commands_account.py
index 4d28802..2c07780 100644
--- a/tests/unit/test_commands_account.py
+++ b/tests/unit/test_commands_account.py
@@ -2,6 +2,7 @@ import unittest
from unittest.mock import MagicMock
from firefly_cli.commands import account as acct
from firefly_cli.context import Context
+from firefly_cli.errors import FireflyError
def make_ctx():
client = MagicMock()
@@ -25,3 +26,52 @@ class TestAccountCmd(unittest.TestCase):
rc = acct.cmd_balance(args, ctx)
resolver.account.assert_called_once_with("Checking")
self.assertEqual(rc, 0)
+
+class TestAccountCreate(unittest.TestCase):
+ def _args(self, **kw):
+ base = dict(name=None, type=None, opening_balance=None, currency=None)
+ base.update(kw)
+ m = MagicMock()
+ m.configure_mock(**base) # 'name' is reserved in MagicMock ctor, not configure_mock
+ return m
+
+ def test_asset_posts_with_default_role(self):
+ ctx, client, _ = make_ctx()
+ client.request.return_value = {"data": {"id": "9", "attributes": {}}}
+ rc = acct.cmd_create(self._args(name="Savings", type="asset"), ctx)
+ self.assertEqual(rc, 0)
+ method, path = client.request.call_args[0][:2]
+ body = client.request.call_args[1]["body"]
+ self.assertEqual((method, path), ("POST", "/api/v1/accounts"))
+ self.assertEqual(body["name"], "Savings")
+ self.assertEqual(body["type"], "asset")
+ self.assertEqual(body["account_role"], "defaultAsset")
+
+ def test_expense_has_no_role(self):
+ ctx, client, _ = make_ctx()
+ client.request.return_value = {"data": {"id": "9", "attributes": {}}}
+ acct.cmd_create(self._args(name="Rent", type="expense"), ctx)
+ body = client.request.call_args[1]["body"]
+ self.assertNotIn("account_role", body)
+
+ def test_opening_balance_adds_date(self):
+ ctx, client, _ = make_ctx()
+ client.request.return_value = {"data": {"id": "9", "attributes": {}}}
+ acct.cmd_create(
+ self._args(name="Savings", type="asset", opening_balance="500"), ctx)
+ body = client.request.call_args[1]["body"]
+ self.assertEqual(body["opening_balance"], "500")
+ self.assertIn("opening_balance_date", body) # required_with by Firefly
+
+ def test_currency_passed_through(self):
+ ctx, client, _ = make_ctx()
+ client.request.return_value = {"data": {"id": "9", "attributes": {}}}
+ acct.cmd_create(
+ self._args(name="Savings", type="asset", currency="EUR"), ctx)
+ self.assertEqual(client.request.call_args[1]["body"]["currency_code"], "EUR")
+
+ def test_bad_type_is_hard_error_no_request(self):
+ ctx, client, _ = make_ctx()
+ with self.assertRaises(FireflyError):
+ acct.cmd_create(self._args(name="X", type="bogus"), ctx)
+ client.request.assert_not_called()
diff --git a/tests/unit/test_output.py b/tests/unit/test_output.py
index c71a424..2e3e5ea 100644
--- a/tests/unit/test_output.py
+++ b/tests/unit/test_output.py
@@ -29,3 +29,87 @@ class TestOutput(unittest.TestCase):
out = buf.getvalue()
self.assertIn("Checking", out)
self.assertIn("id", out)
+
+ def test_emit_human_drops_nested_columns(self):
+ # dict/list-valued fields would dump unreadable blobs; they must be cut.
+ buf = io.StringIO()
+ with redirect_stdout(buf):
+ emit([{"id": "1", "name": "x", "junk": {"a": 1}, "tags": [1, 2]}],
+ human=True)
+ out = buf.getvalue()
+ self.assertIn("name", out)
+ self.assertNotIn("junk", out)
+ self.assertNotIn("tags", out)
+
+ def test_emit_human_transaction_flattens_splits(self):
+ # The useful fields live in the nested `transactions` split list, and the
+ # raw 12-decimal amount should be trimmed to 2 dp.
+ tx = {"id": "77", "transactions": [{
+ "type": "withdrawal", "date": "2026-06-28T00:00:00+02:00",
+ "amount": "7.400000000000", "currency_code": "EUR",
+ "description": "McDonald", "source_name": "BBVA",
+ "destination_name": "McDonald's", "category_name": "Food"}]}
+ buf = io.StringIO()
+ with redirect_stdout(buf):
+ emit([tx], human=True)
+ out = buf.getvalue()
+ self.assertIn("28/06/2026", out) # Italian date, no time/zone
+ self.assertIn("7.40", out) # amount trimmed to 2 dp
+ self.assertNotIn("7.400000", out)
+ self.assertIn("McDonald's", out) # destination surfaced
+ self.assertIn("Food", out)
+ self.assertNotIn("import_hash", out) # raw blob not dumped
+
+ def test_emit_color_only_on_tty(self):
+ tx = {"id": "1", "transactions": [{"type": "withdrawal",
+ "date": "2026-06-28", "amount": "1", "currency_code": "EUR",
+ "description": "x", "source_name": "a", "destination_name": "b",
+ "category_name": "c"}]}
+ plain = io.StringIO()
+ emit([tx], human=True, stream=plain)
+ self.assertNotIn("\033[", plain.getvalue()) # piped: no ANSI
+
+ class TTY(io.StringIO):
+ def isatty(self):
+ return True
+ tty = TTY()
+ emit([tx], human=True, stream=tty)
+ self.assertIn("\033[31m", tty.getvalue()) # tty: withdrawal is red
+
+ def _emit(self, row):
+ buf = io.StringIO()
+ emit([row], human=True, stream=buf)
+ return buf.getvalue()
+
+ def test_view_account_shows_balance_drops_plumbing(self):
+ # account_role signature -> the account view.
+ out = self._emit({"id": "6", "name": "BBVA", "type": "asset",
+ "account_role": "defaultAsset",
+ "current_balance": "1590.92", "currency_code": "EUR",
+ "active": True, "iban": "IT68...", "notes": "secret"})
+ self.assertIn("BBVA", out)
+ self.assertIn("1590.92", out)
+ self.assertIn("currency_code", out)
+ self.assertNotIn("iban", out) # plumbing column dropped
+ self.assertNotIn("secret", out)
+
+ def test_view_tag(self):
+ out = self._emit({"id": "9", "tag": "2026", "description": "yr",
+ "zoom_level": None, "latitude": None})
+ self.assertIn("2026", out)
+ self.assertIn("description", out)
+ self.assertNotIn("zoom_level", out)
+
+ def test_view_account_balance(self):
+ # The balance handler emits id+name+current_balance (no account_role).
+ out = self._emit({"id": "6", "name": "BBVA",
+ "current_balance": "1590.92"})
+ self.assertIn("current_balance", out)
+ self.assertIn("1590.92", out)
+
+ def test_view_category_name_only(self):
+ out = self._emit({"id": "2", "name": "Food", "spent": [],
+ "primary_currency_code": "EUR", "notes": "junk"})
+ self.assertIn("Food", out)
+ self.assertNotIn("primary_currency_code", out)
+ self.assertNotIn("spent", out)