import os import sys from unittest.mock import MagicMock, patch import pytest sys.path.insert( 0, os.path.abspath("../../..") ) # Adds the parent directory to the system-path from litellm.proxy.proxy_cli import ProxyInitializationHelpers import builtins from litellm.proxy.proxy_cli import run_separate_health_app import types class TestProxyInitializationHelpers: @patch("importlib.metadata.version") @patch("click.echo") def test_echo_litellm_version(self, mock_echo, mock_version): # Setup mock_version.return_value = "1.0.0" # Execute ProxyInitializationHelpers._echo_litellm_version() # Assert mock_version.assert_called_once_with("litellm") mock_echo.assert_called_once_with("\nLiteLLM: Current Version = 1.0.0\n") @patch("httpx.get") @patch("builtins.print") @patch("json.dumps") def test_run_health_check(self, mock_dumps, mock_print, mock_get): # Setup mock_response = MagicMock() mock_response.json.return_value = {"status": "healthy"} mock_get.return_value = mock_response mock_dumps.return_value = '{"status": "healthy"}' # Execute ProxyInitializationHelpers._run_health_check("localhost", 8000) # Assert mock_get.assert_called_once_with(url="http://localhost:8000/health") mock_response.json.assert_called_once() mock_dumps.assert_called_once_with({"status": "healthy"}, indent=4) @patch("openai.OpenAI") @patch("click.echo") @patch("builtins.print") def test_run_test_chat_completion(self, mock_print, mock_echo, mock_openai): # Setup mock_client = MagicMock() mock_openai.return_value = mock_client mock_response = MagicMock() mock_client.chat.completions.create.return_value = mock_response mock_stream_response = MagicMock() mock_stream_response.__iter__.return_value = [MagicMock(), MagicMock()] mock_client.chat.completions.create.side_effect = [ mock_response, mock_stream_response, ] # Execute with pytest.raises(ValueError, match="Invalid test value"): ProxyInitializationHelpers._run_test_chat_completion( "localhost", 8000, "gpt-3.5-turbo", True ) # Test with valid string test value ProxyInitializationHelpers._run_test_chat_completion( "localhost", 8000, "gpt-3.5-turbo", "http://test-url" ) # Assert mock_openai.assert_called_once_with( api_key="My API Key", base_url="http://test-url" ) mock_client.chat.completions.create.assert_called() def test_get_default_unvicorn_init_args(self): # Test without log_config args = ProxyInitializationHelpers._get_default_unvicorn_init_args( "localhost", 8000 ) assert args["app"] == "litellm.proxy.proxy_server:app" assert args["host"] == "localhost" assert args["port"] == 8000 # Test with log_config args = ProxyInitializationHelpers._get_default_unvicorn_init_args( "localhost", 8000, "log_config.json" ) assert args["log_config"] == "log_config.json" # Test with json_logs=True with patch("litellm.json_logs", True): args = ProxyInitializationHelpers._get_default_unvicorn_init_args( "localhost", 8000 ) assert args["log_config"] is None # Test with keepalive_timeout args = ProxyInitializationHelpers._get_default_unvicorn_init_args( "localhost", 8000, None, 60 ) assert args["timeout_keep_alive"] == 60 # Test with both log_config and keepalive_timeout args = ProxyInitializationHelpers._get_default_unvicorn_init_args( "localhost", 8000, "log_config.json", 120 ) assert args["log_config"] == "log_config.json" assert args["timeout_keep_alive"] == 120 @patch("asyncio.run") @patch("builtins.print") def test_init_hypercorn_server(self, mock_print, mock_asyncio_run): # Setup mock_app = MagicMock() # Execute ProxyInitializationHelpers._init_hypercorn_server( mock_app, "localhost", 8000, None, None, None ) # Assert mock_asyncio_run.assert_called_once() # Test with SSL ProxyInitializationHelpers._init_hypercorn_server( mock_app, "localhost", 8000, "cert.pem", "key.pem", "ECDHE" ) @patch("subprocess.Popen") def test_run_ollama_serve(self, mock_popen): # Execute ProxyInitializationHelpers._run_ollama_serve() # Assert mock_popen.assert_called_once() # Test exception handling mock_popen.side_effect = Exception("Test exception") ProxyInitializationHelpers._run_ollama_serve() # Should not raise @patch("socket.socket") def test_is_port_in_use(self, mock_socket): # Setup for port in use mock_socket_instance = MagicMock() mock_socket_instance.connect_ex.return_value = 0 mock_socket.return_value.__enter__.return_value = mock_socket_instance # Execute and Assert assert ProxyInitializationHelpers._is_port_in_use(8000) is True # Setup for port not in use mock_socket_instance.connect_ex.return_value = 1 # Execute and Assert assert ProxyInitializationHelpers._is_port_in_use(8000) is False def test_get_loop_type(self): # Test on Windows with patch("sys.platform", "win32"): assert ProxyInitializationHelpers._get_loop_type() is None # Test on Linux with patch("sys.platform", "linux"): assert ProxyInitializationHelpers._get_loop_type() == "uvloop" @patch.dict(os.environ, {}, clear=True) def test_database_url_construction_with_special_characters(self): # Setup environment variables with special characters that need escaping test_env = { "DATABASE_HOST": "localhost:5432", "DATABASE_USERNAME": "user@with+special", "DATABASE_PASSWORD": "pass&word!@#$%", "DATABASE_NAME": "db_name/test", } with patch.dict(os.environ, test_env): # Call the relevant function - we'll need to extract the database URL construction logic # This is simulating what happens in the run_server function when database_url is None import urllib.parse from litellm.proxy.proxy_cli import append_query_params database_host = os.environ["DATABASE_HOST"] database_username = os.environ["DATABASE_USERNAME"] database_password = os.environ["DATABASE_PASSWORD"] database_name = os.environ["DATABASE_NAME"] # Test the URL encoding part database_username_enc = urllib.parse.quote_plus(database_username) database_password_enc = urllib.parse.quote_plus(database_password) database_name_enc = urllib.parse.quote_plus(database_name) # Construct DATABASE_URL from the provided variables database_url = f"postgresql://{database_username_enc}:{database_password_enc}@{database_host}/{database_name_enc}" # Assert the correct URL was constructed with properly escaped characters expected_url = "postgresql://user%40with%2Bspecial:pass%26word%21%40%23%24%25@localhost:5432/db_name%2Ftest" assert database_url == expected_url # Test appending query parameters params = {"connection_limit": 10, "pool_timeout": 60} modified_url = append_query_params(database_url, params) assert "connection_limit=10" in modified_url assert "pool_timeout=60" in modified_url @patch("uvicorn.run") @patch("builtins.print") def test_skip_server_startup(self, mock_print, mock_uvicorn_run): """Test that the skip_server_startup flag prevents server startup when True""" from click.testing import CliRunner from litellm.proxy.proxy_cli import run_server runner = CliRunner() mock_app = MagicMock() mock_proxy_config = MagicMock() mock_key_mgmt = MagicMock() mock_save_worker_config = MagicMock() with patch.dict( "sys.modules", { "proxy_server": MagicMock( app=mock_app, ProxyConfig=mock_proxy_config, KeyManagementSettings=mock_key_mgmt, save_worker_config=mock_save_worker_config, ) }, ), patch( "litellm.proxy.proxy_cli.ProxyInitializationHelpers._get_default_unvicorn_init_args" ) as mock_get_args: mock_get_args.return_value = { "app": "litellm.proxy.proxy_server:app", "host": "localhost", "port": 8000, } result = runner.invoke(run_server, ["--local", "--skip_server_startup"]) assert result.exit_code == 0 mock_uvicorn_run.assert_not_called() mock_print.assert_any_call( "LiteLLM: Setup complete. Skipping server startup as requested." ) mock_uvicorn_run.reset_mock() mock_print.reset_mock() result = runner.invoke(run_server, ["--local"]) assert result.exit_code == 0 mock_uvicorn_run.assert_called_once() @patch("uvicorn.run") @patch("builtins.print") def test_keepalive_timeout_flag(self, mock_print, mock_uvicorn_run): """Test that the keepalive_timeout flag is properly passed to uvicorn""" from click.testing import CliRunner from litellm.proxy.proxy_cli import run_server runner = CliRunner() mock_app = MagicMock() mock_proxy_config = MagicMock() mock_key_mgmt = MagicMock() mock_save_worker_config = MagicMock() with patch.dict( "sys.modules", { "proxy_server": MagicMock( app=mock_app, ProxyConfig=mock_proxy_config, KeyManagementSettings=mock_key_mgmt, save_worker_config=mock_save_worker_config, ) }, ), patch( "litellm.proxy.proxy_cli.ProxyInitializationHelpers._get_default_unvicorn_init_args" ) as mock_get_args: mock_get_args.return_value = { "app": "litellm.proxy.proxy_server:app", "host": "localhost", "port": 8000, "timeout_keep_alive": 30, } result = runner.invoke(run_server, ["--local", "--keepalive_timeout", "30"]) assert result.exit_code == 0 mock_get_args.assert_called_once_with( host="0.0.0.0", port=4000, log_config=None, keepalive_timeout=30, ) mock_uvicorn_run.assert_called_once() # Check that the uvicorn.run was called with the timeout_keep_alive parameter call_args = mock_uvicorn_run.call_args assert call_args[1]["timeout_keep_alive"] == 30 def test_run_separate_health_app_runs_only_when_flag(monkeypatch): # Patch print to track output printed = [] monkeypatch.setattr(builtins, "print", lambda *args, **kwargs: printed.append(args)) # Patch threading.Thread to track if it's started started = {} class DummyThread: def __init__(self, target, daemon): started['called'] = True def start(self): started['started'] = True monkeypatch.setattr("threading.Thread", DummyThread) # Patch uvicorn.run to prevent actual server start monkeypatch.setattr("uvicorn.run", lambda *a, **k: None) # Patch FastAPI import by patching sys.modules['fastapi'] class DummyFastAPI: def __init__(self, *a, **k): pass def include_router(self, router): pass dummy_fastapi = types.ModuleType("fastapi") dummy_fastapi.FastAPI = DummyFastAPI monkeypatch.setitem(sys.modules, "fastapi", dummy_fastapi) # Patch the health_endpoints module in sys.modules dummy_health_endpoints = types.ModuleType("litellm.proxy.health_endpoints._health_endpoints") dummy_health_endpoints.router = object() monkeypatch.setitem(sys.modules, "litellm.proxy.health_endpoints._health_endpoints", dummy_health_endpoints) # Should NOT run if flag is not set monkeypatch.setenv("SEPARATE_HEALTH_APP", "0") started.clear() printed.clear() run_separate_health_app() assert not started, "Should not start thread if flag is not '1'" assert not printed, "Should not print if flag is not '1'" # Should run if flag is set monkeypatch.setenv("SEPARATE_HEALTH_APP", "1") started.clear() printed.clear() run_separate_health_app() assert started.get('called'), "Should create thread if flag is '1'" assert started.get('started') is None or started.get('started'), "Should start thread if flag is '1'" assert any("LiteLLM Health Endpoints" in str(args) for args in printed), "Should print health endpoint message if flag is '1'"