aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--firefly_cli/commands/budget.py33
-rw-r--r--tests/unit/test_commands_budget.py37
2 files changed, 70 insertions, 0 deletions
diff --git a/firefly_cli/commands/budget.py b/firefly_cli/commands/budget.py
index 990d2ad..3994958 100644
--- a/firefly_cli/commands/budget.py
+++ b/firefly_cli/commands/budget.py
@@ -2,6 +2,7 @@
import calendar
from datetime import date
from firefly_cli import registry, output
+from firefly_cli.errors import FireflyError
def _current_month():
@@ -72,3 +73,35 @@ def cmd_create(args, ctx):
resp = ctx.client.request("POST", "/api/v1/budgets", body=body)
output.emit(output.unwrap(resp), human=ctx.human)
return 0
+
+
+def _ref_arg(p):
+ p.add_argument("ref", help="budget name or id")
+
+def _delete_args(p):
+ _ref_arg(p)
+ p.add_argument("--yes", action="store_true", help="confirm deletion (required)")
+
+@registry.command("budget delete", help="delete a budget by name or id (requires --yes)", args=_delete_args)
+def cmd_delete(args, ctx):
+ if not args.yes:
+ raise FireflyError("budget delete needs --yes to confirm.")
+ b = ctx.resolver.budget(args.ref)
+ ctx.client.request("DELETE", f"/api/v1/budgets/{b['id']}")
+ output.emit({"deleted": b["id"], "name": b.get("name")}, human=ctx.human)
+ return 0
+
+def _set_active(ctx, ref, active):
+ b = ctx.resolver.budget(ref)
+ resp = ctx.client.request("PUT", f"/api/v1/budgets/{b['id']}",
+ body={"active": active})
+ output.emit(output.unwrap(resp), human=ctx.human)
+ return 0
+
+@registry.command("budget enable", help="mark a budget active", args=_ref_arg)
+def cmd_enable(args, ctx):
+ return _set_active(ctx, args.ref, True)
+
+@registry.command("budget disable", help="mark a budget inactive", args=_ref_arg)
+def cmd_disable(args, ctx):
+ return _set_active(ctx, args.ref, False)
diff --git a/tests/unit/test_commands_budget.py b/tests/unit/test_commands_budget.py
index 26e9dae..0b1d89c 100644
--- a/tests/unit/test_commands_budget.py
+++ b/tests/unit/test_commands_budget.py
@@ -3,6 +3,7 @@ import unittest
from unittest.mock import MagicMock
from types import SimpleNamespace
import firefly_cli.commands.budget as budget
+from firefly_cli.errors import FireflyError
def _ctx(client=None, resolver=None):
@@ -82,5 +83,41 @@ class TestBudgetSpent(unittest.TestCase):
self.assertEqual(captured["rows"][0]["spent"], "-15.00")
+class TestBudgetLifecycle(unittest.TestCase):
+ def _resolver(self):
+ r = MagicMock()
+ r.budget.return_value = {"id": "5", "name": "Rent"}
+ return r
+
+ def test_delete_requires_yes(self):
+ args = SimpleNamespace(ref="Rent", yes=False)
+ with self.assertRaises(FireflyError):
+ budget.cmd_delete(args, _ctx(resolver=self._resolver()))
+
+ def test_delete_with_yes_calls_delete(self):
+ client = MagicMock()
+ args = SimpleNamespace(ref="Rent", yes=True)
+ rc = budget.cmd_delete(args, _ctx(client=client, resolver=self._resolver()))
+ self.assertEqual(rc, 0)
+ client.request.assert_called_with("DELETE", "/api/v1/budgets/5")
+
+ def test_enable_sets_active_true(self):
+ client = MagicMock()
+ client.request.return_value = {"data": {"id": "5", "attributes": {}}}
+ args = SimpleNamespace(ref="Rent")
+ budget.cmd_enable(args, _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.assertTrue(body["active"])
+
+ def test_disable_sets_active_false(self):
+ client = MagicMock()
+ client.request.return_value = {"data": {"id": "5", "attributes": {}}}
+ args = SimpleNamespace(ref="Rent")
+ budget.cmd_disable(args, _ctx(client=client, resolver=self._resolver()))
+ self.assertFalse(client.request.call_args[1]["body"]["active"])
+
+
if __name__ == "__main__":
unittest.main()