aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-07-03 17:49:20 +0200
committerDanilo M. <danix@danix.xyz>2026-07-03 17:49:20 +0200
commite30b3f203059d8d116f77185f0764c391cae07dc (patch)
treee7a322c63be7100897f778ed9be680677fa0c69f
parente0843da28eaa5828665a700b8a06f7fe1e27d2b8 (diff)
downloadfirefly-cli-e30b3f203059d8d116f77185f0764c391cae07dc.tar.gz
firefly-cli-e30b3f203059d8d116f77185f0764c391cae07dc.zip
feat(budget): limit list and limit set
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-rw-r--r--firefly_cli/commands/budget.py28
-rw-r--r--tests/unit/test_commands_budget.py39
2 files changed, 67 insertions, 0 deletions
diff --git a/firefly_cli/commands/budget.py b/firefly_cli/commands/budget.py
index 3994958..733d092 100644
--- a/firefly_cli/commands/budget.py
+++ b/firefly_cli/commands/budget.py
@@ -105,3 +105,31 @@ def cmd_enable(args, ctx):
@registry.command("budget disable", help="mark a budget inactive", args=_ref_arg)
def cmd_disable(args, ctx):
return _set_active(ctx, args.ref, False)
+
+
+@registry.command("budget limit list", help="list a budget's limits (name or id)", args=_ref_arg)
+def cmd_limit_list(args, ctx):
+ b = ctx.resolver.budget(args.ref)
+ resp = ctx.client.request("GET", f"/api/v1/budgets/{b['id']}/limits")
+ output.emit(output.unwrap(resp), human=ctx.human)
+ return 0
+
+def _limit_set_args(p):
+ _ref_arg(p)
+ p.add_argument("--amount", required=True, help="limit amount (positive)")
+ p.add_argument("--start", default=None, help="YYYY-MM-DD (default: 1st of this month)")
+ p.add_argument("--end", default=None, help="YYYY-MM-DD (default: last of this month)")
+ p.add_argument("--currency", default=None, help="currency code, e.g. EUR")
+
+@registry.command("budget limit set", help="set (create) a spending limit for a budget over a period", args=_limit_set_args)
+def cmd_limit_set(args, ctx):
+ b = ctx.resolver.budget(args.ref)
+ first, last = _current_month()
+ body = {"amount": str(args.amount),
+ "start": args.start or first,
+ "end": args.end or last}
+ if args.currency:
+ body["currency_code"] = args.currency
+ resp = ctx.client.request("POST", f"/api/v1/budgets/{b['id']}/limits", body=body)
+ output.emit(output.unwrap(resp), human=ctx.human)
+ return 0
diff --git a/tests/unit/test_commands_budget.py b/tests/unit/test_commands_budget.py
index 0b1d89c..15e89c4 100644
--- a/tests/unit/test_commands_budget.py
+++ b/tests/unit/test_commands_budget.py
@@ -119,5 +119,44 @@ class TestBudgetLifecycle(unittest.TestCase):
self.assertFalse(client.request.call_args[1]["body"]["active"])
+class TestBudgetLimit(unittest.TestCase):
+ def _resolver(self):
+ r = MagicMock()
+ r.budget.return_value = {"id": "5", "name": "Rent"}
+ return r
+
+ def test_limit_list(self):
+ client = MagicMock()
+ client.request.return_value = {"data": []}
+ args = SimpleNamespace(ref="Rent")
+ rc = budget.cmd_limit_list(args, _ctx(client=client, resolver=self._resolver()))
+ self.assertEqual(rc, 0)
+ client.request.assert_called_with("GET", "/api/v1/budgets/5/limits")
+
+ def test_limit_set_default_month(self):
+ client = MagicMock()
+ client.request.return_value = {"data": {"id": "1", "attributes": {}}}
+ args = SimpleNamespace(ref="Rent", amount="800", start=None, end=None,
+ currency=None)
+ budget.cmd_limit_set(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), ("POST", "/api/v1/budgets/5/limits"))
+ self.assertEqual(body["amount"], "800")
+ self.assertTrue(body["start"].endswith("-01"))
+ self.assertEqual(body["start"][:7], body["end"][:7])
+
+ def test_limit_set_explicit_range_and_currency(self):
+ client = MagicMock()
+ client.request.return_value = {"data": {"id": "1", "attributes": {}}}
+ args = SimpleNamespace(ref="Rent", amount="800", start="2026-02-01",
+ end="2026-02-28", currency="EUR")
+ budget.cmd_limit_set(args, _ctx(client=client, resolver=self._resolver()))
+ body = client.request.call_args[1]["body"]
+ self.assertEqual(body["start"], "2026-02-01")
+ self.assertEqual(body["end"], "2026-02-28")
+ self.assertEqual(body["currency_code"], "EUR")
+
+
if __name__ == "__main__":
unittest.main()