diff --git a/router/main.py b/router/main.py index 1b23603b..2487b4b6 100644 --- a/router/main.py +++ b/router/main.py @@ -1291,10 +1291,11 @@ async def classify_request( return "agent-advanced-core", latency, False, "advanced (exception)" -def _read_json_file_sync(file_path: str) -> dict: - """Helper to read JSON files synchronously.""" - with open(file_path, "r") as f: - return json.load(f) +async def _read_json_file_async(file_path: str) -> dict: + """Helper to read JSON files asynchronously.""" + async with aiofiles.open(file_path, "r", encoding="utf-8") as f: + content = await f.read() + return json.loads(content) @@ -1307,7 +1308,7 @@ async def get_gemini_oauth_status() -> dict: "detail": "No oauth_creds.json found", "expiry_ms": 0, } - data = await asyncio.to_thread(_read_json_file_sync, GEMINI_OAUTH_CREDS_PATH) + data = await _read_json_file_async(GEMINI_OAUTH_CREDS_PATH) access_token = data.get("access_token") expiry_ms = data.get("expiry_date", 0) current_ms = int(time.time() * 1000) diff --git a/router/tests/test_get_gemini_oauth_status.py b/router/tests/test_get_gemini_oauth_status.py index 016b5356..89cfc3be 100644 --- a/router/tests/test_get_gemini_oauth_status.py +++ b/router/tests/test_get_gemini_oauth_status.py @@ -1,6 +1,6 @@ import pytest import json -from unittest.mock import patch, mock_open +from unittest.mock import patch, AsyncMock from router import main @@ -13,8 +13,11 @@ async def test_get_gemini_oauth_status_missing_file(): @pytest.mark.asyncio async def test_get_gemini_oauth_status_no_access_token(): mock_data = {"expiry_date": 1234567890000} + mock_file = AsyncMock() + mock_file.read.return_value = json.dumps(mock_data) + mock_file.__aenter__.return_value = mock_file with patch("os.path.exists", return_value=True), \ - patch("builtins.open", mock_open(read_data=json.dumps(mock_data))): + patch("aiofiles.open", return_value=mock_file): result = await main.get_gemini_oauth_status() assert result == {"status": "missing", "detail": "No access token in file", "expiry_ms": 0} @@ -31,8 +34,11 @@ async def test_get_gemini_oauth_status_scenarios(delta, expected_status, expecte current_time_ms = 1000000000000 expiry_ms = current_time_ms + delta mock_data = {"access_token": "test_token", "expiry_date": expiry_ms} + mock_file = AsyncMock() + mock_file.read.return_value = json.dumps(mock_data) + mock_file.__aenter__.return_value = mock_file with patch("os.path.exists", return_value=True), \ - patch("builtins.open", mock_open(read_data=json.dumps(mock_data))), \ + patch("aiofiles.open", return_value=mock_file), \ patch("time.time", return_value=current_time_ms / 1000.0): result = await main.get_gemini_oauth_status() assert result == {"status": expected_status, "detail": expected_detail, "expiry_ms": expiry_ms} @@ -40,6 +46,6 @@ async def test_get_gemini_oauth_status_scenarios(delta, expected_status, expecte @pytest.mark.asyncio async def test_get_gemini_oauth_status_exception(): with patch("os.path.exists", return_value=True), \ - patch("builtins.open", side_effect=Exception("Test error")): + patch("aiofiles.open", side_effect=Exception("Test error")): result = await main.get_gemini_oauth_status() assert result == {"status": "error", "detail": "Test error", "expiry_ms": 0} diff --git a/router/tests/test_read_json_file_sync.py b/router/tests/test_read_json_file_async.py similarity index 51% rename from router/tests/test_read_json_file_sync.py rename to router/tests/test_read_json_file_async.py index 4d385d6d..85ae0044 100644 --- a/router/tests/test_read_json_file_sync.py +++ b/router/tests/test_read_json_file_async.py @@ -1,14 +1,18 @@ import json -from unittest.mock import mock_open, patch +from unittest.mock import patch, AsyncMock import pytest from router import main -def test_read_json_file_sync_success(): +@pytest.mark.asyncio +async def test_read_json_file_async_success(): mock_data = '{"key": "value"}' - with patch("builtins.open", mock_open(read_data=mock_data)): - result = main._read_json_file_sync("dummy_path.json") + mock_file = AsyncMock() + mock_file.read.return_value = mock_data + mock_file.__aenter__.return_value = mock_file + with patch("aiofiles.open", return_value=mock_file): + result = await main._read_json_file_async("dummy_path.json") assert result == {"key": "value"} @@ -16,7 +20,7 @@ def test_read_json_file_sync_success(): "mock_kwargs, expected_exc, match_msg", [ ( - {"new": mock_open(read_data='{"key": "value"')}, + {"return_value": AsyncMock(__aenter__=AsyncMock(return_value=AsyncMock(read=AsyncMock(return_value='{"key": "value"'))))}, json.JSONDecodeError, r"Unterminated string starting at|Expecting", ), @@ -32,7 +36,8 @@ def test_read_json_file_sync_success(): ), ], ) -def test_read_json_file_sync_errors(mock_kwargs, expected_exc, match_msg): - with patch("builtins.open", **mock_kwargs): +@pytest.mark.asyncio +async def test_read_json_file_async_errors(mock_kwargs, expected_exc, match_msg): + with patch("aiofiles.open", **mock_kwargs): with pytest.raises(expected_exc, match=match_msg): - main._read_json_file_sync("dummy_path.json") + await main._read_json_file_async("dummy_path.json")