"""Tests for foreign exchange functionality.""" import json import os import tempfile from decimal import Decimal from unittest.mock import Mock, patch import pytest from agenda.fx import read_cached_rates class TestReadCachedRates: """Test the read_cached_rates function.""" def test_read_cached_rates_none_filename(self) -> None: """Test with None filename returns empty dict.""" result = read_cached_rates(None, ["USD", "EUR"]) assert result == {} def test_read_cached_rates_valid_file(self) -> None: """Test reading valid cached rates file.""" currencies = ["USD", "EUR", "JPY"] data = { "quotes": { "GBPUSD": 1.25, "GBPEUR": 1.15, "GBPJPY": 150.0, "GBPCAD": 1.70 # Not requested } } with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: json.dump(data, f) filepath = f.name try: result = read_cached_rates(filepath, currencies) assert len(result) == 3 assert result["USD"] == Decimal("1.25") assert result["EUR"] == Decimal("1.15") assert result["JPY"] == Decimal("150.0") assert "CAD" not in result # Not requested finally: os.unlink(filepath) def test_read_cached_rates_missing_currencies(self) -> None: """Test with some currencies missing from data.""" currencies = ["USD", "EUR", "CHF"] # CHF not in data data = { "quotes": { "GBPUSD": 1.25, "GBPEUR": 1.15 # GBPCHF missing } } with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: json.dump(data, f) filepath = f.name try: result = read_cached_rates(filepath, currencies) assert len(result) == 2 assert result["USD"] == Decimal("1.25") assert result["EUR"] == Decimal("1.15") assert "CHF" not in result # Missing from data finally: os.unlink(filepath) def test_read_cached_rates_empty_currencies_list(self) -> None: """Test with empty currencies list.""" data = { "quotes": { "GBPUSD": 1.25, "GBPEUR": 1.15 } } with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: json.dump(data, f) filepath = f.name try: result = read_cached_rates(filepath, []) assert result == {} finally: os.unlink(filepath) def test_read_cached_rates_no_quotes_key(self) -> None: """Test with data missing quotes key.""" currencies = ["USD", "EUR"] data = {"other_key": "value"} # No quotes key with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: json.dump(data, f) filepath = f.name try: with pytest.raises(KeyError): read_cached_rates(filepath, currencies) finally: os.unlink(filepath) def test_read_cached_rates_file_not_found(self) -> None: """Test error handling when file doesn't exist.""" with pytest.raises(FileNotFoundError): read_cached_rates("/nonexistent/file.json", ["USD"]) def test_read_cached_rates_invalid_json(self) -> None: """Test error handling with invalid JSON.""" with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: f.write("invalid json content") filepath = f.name try: with pytest.raises(json.JSONDecodeError): read_cached_rates(filepath, ["USD"]) finally: os.unlink(filepath) def test_read_cached_rates_decimal_precision(self) -> None: """Test that rates are returned as Decimal with proper precision.""" currencies = ["USD"] data = { "quotes": { "GBPUSD": 1.234567890123456789 # High precision } } with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: json.dump(data, f) filepath = f.name try: result = read_cached_rates(filepath, currencies) assert isinstance(result["USD"], Decimal) # Should preserve reasonable precision from the JSON # Python's JSON precision may be limited to float precision expected = Decimal("1.234567890123456789") assert abs(result["USD"] - expected) < Decimal("0.0000000000000001") finally: os.unlink(filepath) def test_read_cached_rates_various_currency_codes(self) -> None: """Test with various currency codes.""" currencies = ["USD", "EUR", "JPY", "CHF", "CAD", "AUD"] data = { "quotes": { "GBPUSD": 1.25, "GBPEUR": 1.15, "GBPJPY": 150.0, "GBPCHF": 1.12, "GBPCAD": 1.70, "GBPAUD": 1.85 } } with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: json.dump(data, f) filepath = f.name try: result = read_cached_rates(filepath, currencies) assert len(result) == 6 for currency in currencies: assert currency in result assert isinstance(result[currency], Decimal) finally: os.unlink(filepath)