aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-07-05 19:17:04 +0200
committerDanilo M. <danix@danix.xyz>2026-07-05 19:17:04 +0200
commit50cb852130986249cacec411e23392a8610e12f1 (patch)
treed49c544811f3f088b966046c859412f1b1015162 /tests
parent2f768bb3373105e96d0cbc6fb4ba37c777bbca25 (diff)
downloadfirefly-cli-master.tar.gz
firefly-cli-master.zip
feat(budget): tx edit --budget and budget update (v0.5.0)HEADv0.5.0master
Add two deferred budget follow-ups: - tx edit --budget: re-assign a budget on an existing transaction, mirroring the existing tx add --budget flag (name/id resolved, hard error on miss). Resolves the ISSUES.md backfill wish. - budget update: rename a budget and/or edit its auto-budget fields via PUT /budgets/{id}; only fields passed are sent, empty is a hard error. New command adds to the CLI surface without breaking callers, so this is a MINOR bump (0.4.1 -> 0.5.0). Docs (README, SKILL.md), completion, and TODO synced. The tx add --budget-id item stays open (still YAGNI). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/unit/test_commands_budget.py42
-rw-r--r--tests/unit/test_commands_transaction.py22
2 files changed, 60 insertions, 4 deletions
diff --git a/tests/unit/test_commands_budget.py b/tests/unit/test_commands_budget.py
index b365d27..96452c1 100644
--- a/tests/unit/test_commands_budget.py
+++ b/tests/unit/test_commands_budget.py
@@ -119,6 +119,47 @@ class TestBudgetLifecycle(unittest.TestCase):
self.assertFalse(client.request.call_args[1]["body"]["active"])
+class TestBudgetUpdate(unittest.TestCase):
+ def _resolver(self):
+ r = MagicMock()
+ r.budget.return_value = {"id": "5", "name": "Rent"}
+ return r
+
+ def _args(self, **kw):
+ base = dict(ref="Rent", name=None, currency=None, auto_budget_amount=None,
+ auto_budget_period=None, auto_budget_type=None)
+ base.update(kw)
+ return SimpleNamespace(**base)
+
+ def test_rename_puts_name(self):
+ client = MagicMock()
+ client.request.return_value = {"data": {"id": "5", "attributes": {}}}
+ budget.cmd_update(self._args(name="Housing"),
+ _ctx(client=client, resolver=self._resolver()))
+ method, path = client.request.call_args[0][:2]
+ body = client.request.call_args[1]["body"]
+ self.assertEqual((method, path), ("PUT", "/api/v1/budgets/5"))
+ self.assertEqual(body, {"name": "Housing"})
+
+ def test_auto_budget_fields(self):
+ client = MagicMock()
+ client.request.return_value = {"data": {"id": "5", "attributes": {}}}
+ budget.cmd_update(
+ self._args(auto_budget_amount=500, auto_budget_period="monthly",
+ auto_budget_type="reset", currency="EUR"),
+ _ctx(client=client, resolver=self._resolver()))
+ body = client.request.call_args[1]["body"]
+ self.assertEqual(body["auto_budget_amount"], "500")
+ self.assertEqual(body["auto_budget_period"], "monthly")
+ self.assertEqual(body["auto_budget_type"], "reset")
+ self.assertEqual(body["currency_code"], "EUR")
+ self.assertNotIn("name", body)
+
+ def test_no_fields_errors(self):
+ with self.assertRaises(FireflyError):
+ budget.cmd_update(self._args(), _ctx(resolver=self._resolver()))
+
+
class TestBudgetLimit(unittest.TestCase):
def _resolver(self):
r = MagicMock()
@@ -168,6 +209,7 @@ class TestBudgetRegistration(unittest.TestCase):
expected = {
"budget list": budget.cmd_list,
"budget create": budget.cmd_create,
+ "budget update": budget.cmd_update,
"budget delete": budget.cmd_delete,
"budget enable": budget.cmd_enable,
"budget disable": budget.cmd_disable,
diff --git a/tests/unit/test_commands_transaction.py b/tests/unit/test_commands_transaction.py
index 7366b5c..ec4e54c 100644
--- a/tests/unit/test_commands_transaction.py
+++ b/tests/unit/test_commands_transaction.py
@@ -274,7 +274,8 @@ class TestTxEdit(unittest.TestCase):
ctx, client, resolver = make_ctx()
client.request.return_value = {"data": {"id": "9", "attributes": {}}}
args = MagicMock(id="9", amount="12.00", date=None, desc="fixed",
- source=None, dest=None, category=None, tags=None, type=None)
+ source=None, dest=None, category=None, tags=None,
+ budget=None, type=None)
rc = tx.cmd_edit(args, ctx)
self.assertEqual(rc, 0)
method, path = client.request.call_args[0][:2]
@@ -291,7 +292,8 @@ class TestTxEdit(unittest.TestCase):
}[n]
client.request.return_value = {"data": {"id": "9", "attributes": {}}}
args = MagicMock(id="9", amount=None, date=None, desc=None,
- source="BBVA", dest="Medio", category=None, tags=None, type=None)
+ source="BBVA", dest="Medio", category=None, tags=None,
+ budget=None, type=None)
tx.cmd_edit(args, ctx)
split = client.request.call_args[1]["body"]["transactions"][0]
self.assertEqual(split, {"source_id": "3", "destination_id": "4"})
@@ -300,7 +302,8 @@ class TestTxEdit(unittest.TestCase):
ctx, client, resolver = make_ctx()
client.request.return_value = {"data": {"id": "9", "attributes": {}}}
args = MagicMock(id="9", amount=None, date=None, desc=None, source=None,
- dest=None, category="Cat", tags="a, b", type="transfer")
+ dest=None, category="Cat", tags="a, b", budget=None,
+ type="transfer")
tx.cmd_edit(args, ctx)
split = client.request.call_args[1]["body"]["transactions"][0]
self.assertEqual(split,
@@ -311,11 +314,22 @@ class TestTxEdit(unittest.TestCase):
from firefly_cli.errors import FireflyError
ctx, client, _ = make_ctx()
args = MagicMock(id="9", amount=None, date=None, desc=None, source=None,
- dest=None, category=None, tags=None, type=None)
+ dest=None, category=None, tags=None, budget=None, type=None)
with self.assertRaises(FireflyError):
tx.cmd_edit(args, ctx)
client.request.assert_not_called()
+ def test_edit_budget_ref_sets_budget_id(self):
+ ctx, client, resolver = make_ctx()
+ resolver.budget.return_value = {"id": "12", "name": "Groceries"}
+ client.request.return_value = {"data": {"id": "9", "attributes": {}}}
+ args = MagicMock(id="9", amount=None, date=None, desc=None, source=None,
+ dest=None, category=None, tags=None, budget="Groceries",
+ type=None)
+ tx.cmd_edit(args, ctx)
+ split = client.request.call_args[1]["body"]["transactions"][0]
+ self.assertEqual(split, {"budget_id": "12"})
+
class TestTxDelete(unittest.TestCase):
def test_delete_requires_yes(self):