# Copyright (C) 2026 Danilo M. GPL-2.0-only import os, unittest from firefly_cli.client import Client from firefly_cli.resolver import Resolver from firefly_cli.output import unwrap URL = os.environ.get("FIREFLY_TEST_URL") TOKEN = os.environ.get("FIREFLY_TEST_TOKEN") @unittest.skipUnless(URL and TOKEN, "set FIREFLY_TEST_URL and FIREFLY_TEST_TOKEN to run live tests") class TestLive(unittest.TestCase): @classmethod def setUpClass(cls): cls.client = Client(URL, TOKEN) cls.resolver = Resolver(cls.client) def test_about_shape(self): resp = self.client.request("GET", "/api/v1/about") self.assertIn("version", resp["data"]) def test_accounts_envelope_and_balance_field(self): resp = self.client.request("GET", "/api/v1/accounts", params={"type": "asset", "limit": 5}) self.assertIn("data", resp) accs = unwrap(resp) if accs: # Confirm the balance field name assumed by the design. self.assertIn("current_balance", accs[0], "balance field name differs; update account balance cmd + unit test") def test_create_then_delete_transaction(self): # Needs an asset account and an expense account on the TEST account. accs = unwrap(self.client.request("GET", "/api/v1/accounts", params={"type": "asset", "limit": 2})) if len(accs) < 1: self.skipTest("test account has no asset account") exp = unwrap(self.client.request("GET", "/api/v1/accounts", params={"type": "expense", "limit": 1})) if not exp: self.skipTest("test account has no expense account") body = {"transactions": [{ "type": "withdrawal", "date": "2026-06-30", "amount": "0.01", "description": "firefly-cli integration test", "source_id": accs[0]["id"], "destination_id": exp[0]["id"], }]} created = self.client.request("POST", "/api/v1/transactions", body=body) tx_id = created["data"]["id"] try: got = self.client.request("GET", f"/api/v1/transactions/{tx_id}") self.assertEqual(got["data"]["id"], tx_id) finally: self.client.request("DELETE", f"/api/v1/transactions/{tx_id}") def test_piggy_add_remove_roundtrip(self): # Create a piggy bank on the TEST account, drive it through the CLI # handlers, then delete it. import io, contextlib, json from types import SimpleNamespace from firefly_cli.commands import piggy from firefly_cli.errors import FireflyError accs = unwrap(self.client.request("GET", "/api/v1/accounts", params={"type": "asset", "limit": 50})) acc = next((a for a in accs if float(a.get("current_balance") or 0) >= 10), None) if acc is None: self.skipTest("test account has no asset account with >= 10 balance") created = self.client.request("POST", "/api/v1/piggy-banks", body={ "name": "firefly-cli integration piggy", "target_amount": "100", "transaction_currency_code": acc.get("currency_code") or "EUR", "start_date": "2026-01-01", "accounts": [{"account_id": acc["id"], "current_amount": "0"}]}) pb_id = created["data"]["id"] ctx = SimpleNamespace(client=self.client, resolver=self.resolver, human=False) def run(fn, amount): buf = io.StringIO() with contextlib.redirect_stdout(buf): fn(SimpleNamespace(ref=pb_id, amount=amount, account=None), ctx) return json.loads(buf.getvalue())["accounts"][0]["current_amount"] try: from firefly_cli.errors import ApiError try: first = run(piggy.cmd_add, "2.09") except ApiError as e: if "bcsub()" in str(e): self.skipTest("firefly-iii/firefly-iii#13063: piggy PUT with accounts 500s") raise self.assertEqual(float(first), 2.09) self.assertEqual(float(run(piggy.cmd_add, "0.91")), 3.00) self.assertEqual(float(run(piggy.cmd_remove, "1")), 2.00) with self.assertRaises(FireflyError): # more than the account holds run(piggy.cmd_add, "99999999") linked = unwrap(self.client.request("GET", f"/api/v1/piggy-banks/{pb_id}"))["accounts"] self.assertEqual(len(linked), 1) # PUT did not unlink the account finally: self.client.request("DELETE", f"/api/v1/piggy-banks/{pb_id}")