diff options
| author | Danilo M. <danix@danix.xyz> | 2026-07-03 17:43:28 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-07-03 17:43:28 +0200 |
| commit | 5d4d393a0de8a6c429a34dc1a51cde4e703cc033 (patch) | |
| tree | af1a83bebe3c59f6bd28487531ef4b8bb4a89c0d | |
| parent | b125853ab2e5fe3d8d0ad045609a5863e46a607a (diff) | |
| download | firefly-cli-5d4d393a0de8a6c429a34dc1a51cde4e703cc033.tar.gz firefly-cli-5d4d393a0de8a6c429a34dc1a51cde4e703cc033.zip | |
feat(budget): budget create
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| -rw-r--r-- | firefly_cli/commands/budget.py | 31 | ||||
| -rw-r--r-- | tests/unit/test_commands_budget.py | 50 |
2 files changed, 81 insertions, 0 deletions
diff --git a/firefly_cli/commands/budget.py b/firefly_cli/commands/budget.py index 257bba7..990d2ad 100644 --- a/firefly_cli/commands/budget.py +++ b/firefly_cli/commands/budget.py @@ -41,3 +41,34 @@ def cmd_list(args, ctx): rows = [{**r, "spent": _spent_scalar(r)} for r in rows] output.emit(rows, human=ctx.human) return 0 + + +def _create_args(p): + p.add_argument("name", help="budget name (must be unique)") + g = p.add_mutually_exclusive_group() + g.add_argument("--active", dest="active", action="store_true", default=True, + help="create active (default)") + g.add_argument("--inactive", dest="active", action="store_false", + help="create inactive") + p.add_argument("--currency", default=None, help="auto-budget currency code, e.g. EUR") + p.add_argument("--auto-budget-amount", dest="auto_budget_amount", default=None, + help="recurring auto-budget amount") + p.add_argument("--auto-budget-period", dest="auto_budget_period", default=None, + help="daily|weekly|monthly|quarterly|half_year|yearly") + p.add_argument("--auto-budget-type", dest="auto_budget_type", default=None, + help="reset|rollover|adjusted|none") + +@registry.command("budget create", help="create a budget", args=_create_args) +def cmd_create(args, ctx): + body = {"name": args.name, "active": args.active} + if args.auto_budget_amount is not None: + body["auto_budget_amount"] = str(args.auto_budget_amount) + if args.auto_budget_period is not None: + body["auto_budget_period"] = args.auto_budget_period + if args.auto_budget_type is not None: + body["auto_budget_type"] = args.auto_budget_type + if args.currency: + body["currency_code"] = args.currency + resp = ctx.client.request("POST", "/api/v1/budgets", 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 7741840..26e9dae 100644 --- a/tests/unit/test_commands_budget.py +++ b/tests/unit/test_commands_budget.py @@ -32,5 +32,55 @@ class TestBudgetList(unittest.TestCase): {"start": "2026-01-01", "end": "2026-01-31"}) +class TestBudgetCreate(unittest.TestCase): + def test_create_minimal_active(self): + client = MagicMock() + client.request.return_value = {"data": {"id": "9", "attributes": {"name": "Fun"}}} + args = SimpleNamespace(name="Fun", active=True, currency=None, + auto_budget_amount=None, auto_budget_period=None, + auto_budget_type=None) + rc = budget.cmd_create(args, _ctx(client=client)) + 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/budgets")) + self.assertEqual(body["name"], "Fun") + self.assertTrue(body["active"]) + + def test_create_inactive_with_auto_budget(self): + client = MagicMock() + client.request.return_value = {"data": {"id": "9", "attributes": {}}} + args = SimpleNamespace(name="Cap", active=False, currency="EUR", + auto_budget_amount="500", auto_budget_period="monthly", + auto_budget_type="reset") + budget.cmd_create(args, _ctx(client=client)) + body = client.request.call_args[1]["body"] + self.assertFalse(body["active"]) + self.assertEqual(body["auto_budget_amount"], "500") + self.assertEqual(body["auto_budget_type"], "reset") + self.assertEqual(body["currency_code"], "EUR") + + +class TestBudgetSpent(unittest.TestCase): + def test_human_mode_replaces_spent_with_scalar(self): + client = MagicMock() + client.request.return_value = {"data": [ + {"id": "1", "attributes": {"name": "Food", + "spent": [{"sum": "-12.50"}, {"sum": "-2.50"}]}}]} + ctx = _ctx(client=client) + ctx.human = True + captured = {} + import firefly_cli.output as output + orig = output.emit + def spy(data, human=False, stream=None): + captured["rows"] = data + output.emit = spy + try: + budget.cmd_list(SimpleNamespace(start=None, end=None), ctx) + finally: + output.emit = orig + self.assertEqual(captured["rows"][0]["spent"], "-15.00") + + if __name__ == "__main__": unittest.main() |
