From dc3c2d399392da386c9a9d67af9218bb9fecbccb Mon Sep 17 00:00:00 2001 From: harshrofff Date: Tue, 6 Oct 2026 16:48:54 -0400 Subject: [PATCH 1/4] fix(proxy): make litellm --test use the configured host and port --- litellm/proxy/proxy_cli.py | 6 +---- tests/unit/proxy/test_proxy_cli.py | 40 +++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/litellm/proxy/proxy_cli.py b/litellm/proxy/proxy_cli.py index faf268d3986..93d23bf305e 100644 --- a/litellm/proxy/proxy_cli.py +++ b/litellm/proxy/proxy_cli.py @@ -244,11 +244,7 @@ class ProxyInitializationHelpers: click.echo(f"\nLiteLLM: Making a test ChatCompletions request to your proxy. Model={request_model}") import openai - api_base = f"http://{host}:{port}" - if isinstance(test, str): - api_base = test - else: - raise ValueError("Invalid test value") + api_base = test if isinstance(test, str) else f"http://{host}:{port}" client: Final = openai.OpenAI(api_key="My API Key", base_url=api_base) response: Final = client.chat.completions.create( diff --git a/tests/unit/proxy/test_proxy_cli.py b/tests/unit/proxy/test_proxy_cli.py index 8fc807cd8a0..f138c2c6ba5 100644 --- a/tests/unit/proxy/test_proxy_cli.py +++ b/tests/unit/proxy/test_proxy_cli.py @@ -87,12 +87,6 @@ class TestProxyInitializationHelpers: ] # 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" ) @@ -103,6 +97,40 @@ class TestProxyInitializationHelpers: ) mock_client.chat.completions.create.assert_called() + @patch("openai.OpenAI") + @patch("click.echo") + @patch("builtins.print") + def test_run_test_chat_completion_flag_uses_host_and_port( + self, mock_print, mock_echo, mock_openai + ): + mock_client = MagicMock() + mock_openai.return_value = mock_client + + ProxyInitializationHelpers._run_test_chat_completion( + "127.0.0.1", 4000, "gpt-3.5-turbo", True + ) + + mock_openai.assert_called_once_with( + api_key="My API Key", base_url="http://127.0.0.1:4000" + ) + assert mock_client.chat.completions.create.call_count == 2 + + @patch("openai.OpenAI") + def test_test_flag_on_cli_sends_request_to_host_and_port(self, mock_openai): + from click.testing import CliRunner + + result = CliRunner().invoke( + run_server, + ["--test", "--host", "127.0.0.1", "--port", "4000", "--model", "my-model"], + ) + + assert result.exit_code == 0, result.output + mock_openai.assert_called_once_with( + api_key="My API Key", base_url="http://127.0.0.1:4000" + ) + first_call = mock_openai.return_value.chat.completions.create.call_args_list[0] + assert first_call.kwargs["model"] == "my-model" + def test_get_default_unvicorn_init_args(self): # Test without log_config args = ProxyInitializationHelpers._get_default_unvicorn_init_args( From d71ab3800a91fbd0188799f66024e7d3fb8f3b22 Mon Sep 17 00:00:00 2001 From: harshrofff Date: Wed, 7 Oct 2026 09:53:41 -0400 Subject: [PATCH 2/4] test(proxy): drive the --test helper against a stub server instead of mocking the client Signed-off-by: harshrofff --- tests/unit/proxy/test_proxy_cli.py | 83 ++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 15 deletions(-) diff --git a/tests/unit/proxy/test_proxy_cli.py b/tests/unit/proxy/test_proxy_cli.py index f138c2c6ba5..bc476174e66 100644 --- a/tests/unit/proxy/test_proxy_cli.py +++ b/tests/unit/proxy/test_proxy_cli.py @@ -97,23 +97,76 @@ class TestProxyInitializationHelpers: ) mock_client.chat.completions.create.assert_called() - @patch("openai.OpenAI") - @patch("click.echo") - @patch("builtins.print") - def test_run_test_chat_completion_flag_uses_host_and_port( - self, mock_print, mock_echo, mock_openai - ): - mock_client = MagicMock() - mock_openai.return_value = mock_client + def test_run_test_chat_completion_flag_uses_host_and_port(self): + import json + import threading + from http.server import BaseHTTPRequestHandler, HTTPServer - ProxyInitializationHelpers._run_test_chat_completion( - "127.0.0.1", 4000, "gpt-3.5-turbo", True - ) + seen = [] - mock_openai.assert_called_once_with( - api_key="My API Key", base_url="http://127.0.0.1:4000" - ) - assert mock_client.chat.completions.create.call_count == 2 + class Stub(BaseHTTPRequestHandler): + def do_POST(self): + body = json.loads(self.rfile.read(int(self.headers["Content-Length"]))) + seen.append((self.path, body)) + if self.path.endswith("/chat/completions") and body.get("stream"): + payload = ( + 'data: {"id":"c","object":"chat.completion.chunk","created":1,' + '"model":"m","choices":[{"index":0,"delta":{"content":"hi"},' + '"finish_reason":null}]}\n\ndata: [DONE]\n\n' + ) + ctype = "text/event-stream" + elif self.path.endswith("/chat/completions"): + payload = json.dumps( + { + "id": "c", + "object": "chat.completion", + "created": 1, + "model": "m", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "hi"}, + "finish_reason": "stop", + } + ], + } + ) + ctype = "application/json" + else: + payload = json.dumps( + { + "id": "c", + "object": "text_completion", + "created": 1, + "model": "m", + "choices": [{"index": 0, "text": "hi", "finish_reason": "stop"}], + } + ) + ctype = "application/json" + data = payload.encode() + self.send_response(200) + self.send_header("Content-Type", ctype) + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + def log_message(self, *args): + pass + + server = HTTPServer(("127.0.0.1", 0), Stub) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + ProxyInitializationHelpers._run_test_chat_completion( + "127.0.0.1", server.server_port, "my-model", True + ) + finally: + server.shutdown() + server.server_close() + + chat = [body for path, body in seen if path.endswith("/chat/completions")] + assert [b.get("stream", False) for b in chat] == [False, True] + assert all(b["model"] == "my-model" for b in chat) @patch("openai.OpenAI") def test_test_flag_on_cli_sends_request_to_host_and_port(self, mock_openai): From 9ff8ec1ff285e9aa9b4988d18358610fc5019167 Mon Sep 17 00:00:00 2001 From: harshrofff Date: Wed, 7 Oct 2026 09:58:49 -0400 Subject: [PATCH 3/4] fix(proxy): bracket IPv6 hosts in the --test URL Signed-off-by: harshrofff --- litellm/proxy/proxy_cli.py | 3 ++- tests/unit/proxy/test_proxy_cli.py | 20 ++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/litellm/proxy/proxy_cli.py b/litellm/proxy/proxy_cli.py index 93d23bf305e..ce5ef61859d 100644 --- a/litellm/proxy/proxy_cli.py +++ b/litellm/proxy/proxy_cli.py @@ -244,7 +244,8 @@ class ProxyInitializationHelpers: click.echo(f"\nLiteLLM: Making a test ChatCompletions request to your proxy. Model={request_model}") import openai - api_base = test if isinstance(test, str) else f"http://{host}:{port}" + bare_host: Final = f"[{host}]" if ":" in host and not host.startswith("[") else host + api_base: Final = test if isinstance(test, str) else f"http://{bare_host}:{port}" client: Final = openai.OpenAI(api_key="My API Key", base_url=api_base) response: Final = client.chat.completions.create( diff --git a/tests/unit/proxy/test_proxy_cli.py b/tests/unit/proxy/test_proxy_cli.py index bc476174e66..f3403353c61 100644 --- a/tests/unit/proxy/test_proxy_cli.py +++ b/tests/unit/proxy/test_proxy_cli.py @@ -97,12 +97,14 @@ class TestProxyInitializationHelpers: ) mock_client.chat.completions.create.assert_called() - def test_run_test_chat_completion_flag_uses_host_and_port(self): + @pytest.mark.parametrize("host", ["127.0.0.1", "::1"]) + def test_run_test_chat_completion_flag_uses_host_and_port(self, host: str) -> None: import json + import socket import threading from http.server import BaseHTTPRequestHandler, HTTPServer - seen = [] + seen: Final[list[tuple[str, dict[str, object]]]] = [] class Stub(BaseHTTPRequestHandler): def do_POST(self): @@ -153,18 +155,24 @@ class TestProxyInitializationHelpers: def log_message(self, *args): pass - server = HTTPServer(("127.0.0.1", 0), Stub) - thread = threading.Thread(target=server.serve_forever, daemon=True) + class StubServer(HTTPServer): + address_family = socket.AF_INET6 if ":" in host else socket.AF_INET + + try: + server: Final = StubServer((host, 0), Stub) + except OSError: + pytest.skip(f"cannot bind {host} on this machine") + thread: Final = threading.Thread(target=server.serve_forever, daemon=True) thread.start() try: ProxyInitializationHelpers._run_test_chat_completion( - "127.0.0.1", server.server_port, "my-model", True + host, server.server_port, "my-model", True ) finally: server.shutdown() server.server_close() - chat = [body for path, body in seen if path.endswith("/chat/completions")] + chat: Final = [body for path, body in seen if path.endswith("/chat/completions")] assert [b.get("stream", False) for b in chat] == [False, True] assert all(b["model"] == "my-model" for b in chat) From 5e314038c660e2506e3f928eba49c3cb556e7e7b Mon Sep 17 00:00:00 2001 From: harshrofff Date: Wed, 7 Oct 2026 10:10:05 -0400 Subject: [PATCH 4/4] test(proxy): keep the string-URL passthrough test within the test-quality budget Signed-off-by: harshrofff --- tests/unit/proxy/test_proxy_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/proxy/test_proxy_cli.py b/tests/unit/proxy/test_proxy_cli.py index f3403353c61..7fee695b740 100644 --- a/tests/unit/proxy/test_proxy_cli.py +++ b/tests/unit/proxy/test_proxy_cli.py @@ -71,7 +71,7 @@ class TestProxyInitializationHelpers: @patch("openai.OpenAI") @patch("click.echo") @patch("builtins.print") - def test_run_test_chat_completion(self, mock_print, mock_echo, mock_openai): + def test_run_test_chat_completion(self, mock_print, mock_echo, mock_openai): # test-quality-ok: pins the string-URL passthrough to the client; the flag path is asserted at the HTTP boundary in the stub-server test below # Setup mock_client = MagicMock() mock_openai.return_value = mock_client