import unittest from unittest.mock import MagicMock from firefly_cli.resolver import Resolver from firefly_cli.errors import ResolutionError def client_returning(items): c = MagicMock() c.request.return_value = { "data": [ {"id": i["id"], "type": "accounts", "attributes": i["attrs"]} for i in items ] } return c class TestResolver(unittest.TestCase): def test_resolves_unique_account_name(self): c = client_returning([ {"id": "3", "attrs": {"name": "Checking", "type": "asset"}}, {"id": "4", "attrs": {"name": "Savings", "type": "asset"}}, ]) r = Resolver(c) acc = r.account("checking") # case-insensitive self.assertEqual(acc["id"], "3") self.assertEqual(acc["type"], "asset") def test_no_match_raises_with_candidates(self): c = client_returning([{"id": "3", "attrs": {"name": "Checking", "type": "asset"}}]) r = Resolver(c) with self.assertRaises(ResolutionError) as ctx: r.account("Nope") self.assertIn("Checking", str(ctx.exception)) def test_ambiguous_match_raises(self): c = client_returning([ {"id": "3", "attrs": {"name": "Cash", "type": "asset"}}, {"id": "9", "attrs": {"name": "Cash", "type": "asset"}}, ]) r = Resolver(c) with self.assertRaises(ResolutionError) as ctx: r.account("Cash") self.assertIn("3", str(ctx.exception)) self.assertIn("9", str(ctx.exception)) def test_account_by_id_fetches_single(self): c = MagicMock() c.request.return_value = {"data": { "id": "129", "type": "accounts", "attributes": {"name": "Nexi", "type": "revenue"}}} r = Resolver(c) acc = r.account_by_id("129") self.assertEqual(acc, {"id": "129", "name": "Nexi", "type": "revenue"}) c.request.assert_called_once_with("GET", "/api/v1/accounts/129") def _budget_list_resp(budgets): return {"data": [{"id": b["id"], "attributes": {"name": b["name"]}} for b in budgets]} class TestResolverBudget(unittest.TestCase): def test_budget_by_name(self): client = MagicMock() client.request.return_value = _budget_list_resp( [{"id": "3", "name": "Groceries"}, {"id": "4", "name": "Rent"}]) r = Resolver(client) self.assertEqual(r.budget("Groceries")["id"], "3") def test_budget_missing_raises_with_candidates(self): client = MagicMock() client.request.return_value = _budget_list_resp([{"id": "3", "name": "Rent"}]) r = Resolver(client) with self.assertRaises(ResolutionError) as cm: r.budget("Nope") self.assertIn("Rent", str(cm.exception)) def test_budget_numeric_ref_goes_by_id(self): client = MagicMock() client.request.return_value = { "data": {"id": "7", "attributes": {"name": "Fun"}}} r = Resolver(client) got = r.budget("7") self.assertEqual(got["id"], "7") self.assertEqual(got["name"], "Fun") # Must hit the show endpoint, not list. client.request.assert_called_with("GET", "/api/v1/budgets/7")