1+ """Tests for Litellm SSL handling functionality."""
2+
3+ import os
4+ from unittest .mock import patch , MagicMock
5+
6+ import pytest
7+ import requests
8+
9+ from code_assistant_manager .litellm_models import fetch_litellm_models
10+
11+
12+ class TestLitellmSSLHandling :
13+ """Test cases for SSL verification handling in litellm models."""
14+
15+ @patch ("code_assistant_manager.litellm_models.requests.get" )
16+ def test_private_ip_ssl_bypass_10_range (self , mock_get ):
17+ """Test SSL verification bypass for private IP in 10.0.0.0/8 range."""
18+ mock_response = MagicMock ()
19+ mock_response .json .return_value = {"data" : [{"id" : "gpt-4" }]}
20+ mock_get .return_value = mock_response
21+
22+ # Test private IP in 10.x.x.x range
23+ fetch_litellm_models ("test-key" , "http://10.0.0.1:4142" )
24+
25+ # Verify SSL verification was disabled
26+ mock_get .assert_called_once ()
27+ call_kwargs = mock_get .call_args [1 ]
28+ assert call_kwargs ["verify" ] is False
29+
30+ @patch ("code_assistant_manager.litellm_models.requests.get" )
31+ def test_private_ip_ssl_bypass_172_range (self , mock_get ):
32+ """Test SSL verification bypass for private IP in 172.16.0.0/12 range."""
33+ mock_response = MagicMock ()
34+ mock_response .json .return_value = {"data" : [{"id" : "gpt-4" }]}
35+ mock_get .return_value = mock_response
36+
37+ # Test private IP in 172.16.x.x range
38+ fetch_litellm_models ("test-key" , "https://172.16.0.1:4142" )
39+
40+ # Verify SSL verification was disabled
41+ mock_get .assert_called_once ()
42+ call_kwargs = mock_get .call_args [1 ]
43+ assert call_kwargs ["verify" ] is False
44+
45+ @patch ("code_assistant_manager.litellm_models.requests.get" )
46+ def test_private_ip_ssl_bypass_192_range (self , mock_get ):
47+ """Test SSL verification bypass for private IP in 192.168.0.0/16 range."""
48+ mock_response = MagicMock ()
49+ mock_response .json .return_value = {"data" : [{"id" : "gpt-4" }]}
50+ mock_get .return_value = mock_response
51+
52+ # Test private IP in 192.168.x.x range
53+ fetch_litellm_models ("test-key" , "https://192.168.1.100:4142" )
54+
55+ # Verify SSL verification was disabled
56+ mock_get .assert_called_once ()
57+ call_kwargs = mock_get .call_args [1 ]
58+ assert call_kwargs ["verify" ] is False
59+
60+ @patch ("code_assistant_manager.litellm_models.requests.get" )
61+ def test_private_ip_ssl_bypass_loopback (self , mock_get ):
62+ """Test SSL verification bypass for loopback IP 127.0.0.1."""
63+ mock_response = MagicMock ()
64+ mock_response .json .return_value = {"data" : [{"id" : "gpt-4" }]}
65+ mock_get .return_value = mock_response
66+
67+ # Test loopback IP
68+ fetch_litellm_models ("test-key" , "http://127.0.0.1:4142" )
69+
70+ # Verify SSL verification was disabled
71+ mock_get .assert_called_once ()
72+ call_kwargs = mock_get .call_args [1 ]
73+ assert call_kwargs ["verify" ] is False
74+
75+ @patch ("code_assistant_manager.litellm_models.requests.get" )
76+ def test_public_ip_ssl_verification_enabled (self , mock_get ):
77+ """Test SSL verification is enabled for public IPs."""
78+ mock_response = MagicMock ()
79+ mock_response .json .return_value = {"data" : [{"id" : "gpt-4" }]}
80+ mock_get .return_value = mock_response
81+
82+ # Test public IP
83+ fetch_litellm_models ("test-key" , "https://8.8.8.8:4142" )
84+
85+ # Verify SSL verification was enabled (default)
86+ mock_get .assert_called_once ()
87+ call_kwargs = mock_get .call_args [1 ]
88+ assert call_kwargs ["verify" ] is True
89+
90+ @patch ("code_assistant_manager.litellm_models.requests.get" )
91+ def test_hostname_ssl_verification_enabled (self , mock_get ):
92+ """Test SSL verification is enabled for hostnames (not IPs)."""
93+ mock_response = MagicMock ()
94+ mock_response .json .return_value = {"data" : [{"id" : "gpt-4" }]}
95+ mock_get .return_value = mock_response
96+
97+ # Test hostname
98+ fetch_litellm_models ("test-key" , "https://api.example.com:4142" )
99+
100+ # Verify SSL verification was enabled (default)
101+ mock_get .assert_called_once ()
102+ call_kwargs = mock_get .call_args [1 ]
103+ assert call_kwargs ["verify" ] is True
104+
105+ @patch ("code_assistant_manager.litellm_models.requests.get" )
106+ def test_ssl_parsing_error_falls_back_to_verification (self , mock_get ):
107+ """Test that parsing errors fall back to SSL verification enabled."""
108+ mock_response = MagicMock ()
109+ mock_response .json .return_value = {"data" : [{"id" : "gpt-4" }]}
110+ mock_get .return_value = mock_response
111+
112+ # Test invalid URL that causes parsing error
113+ fetch_litellm_models ("test-key" , "not-a-url" )
114+
115+ # Verify SSL verification was enabled (fallback)
116+ mock_get .assert_called_once ()
117+ call_kwargs = mock_get .call_args [1 ]
118+ assert call_kwargs ["verify" ] is True
119+
120+ @patch ("code_assistant_manager.litellm_models.requests.get" )
121+ @patch .dict (os .environ , {"API_KEY_LITELLM" : "test-key" })
122+ def test_endpoint_url_from_config (self , mock_get ):
123+ """Test that endpoint URL is read from config when not in environment."""
124+ mock_response = MagicMock ()
125+ mock_response .json .return_value = {"data" : [{"id" : "gpt-4" }]}
126+ mock_get .return_value = mock_response
127+
128+ from code_assistant_manager .litellm_models import list_models
129+
130+ # Mock ConfigManager
131+ with patch ("code_assistant_manager.litellm_models.ConfigManager" ) as mock_config_class :
132+ mock_config = MagicMock ()
133+ mock_config_class .return_value = mock_config
134+ mock_config .get_endpoint_config .return_value = {
135+ "endpoint" : "https://config-endpoint.com:4142"
136+ }
137+
138+ # Mock stdout to capture output
139+ with patch ("builtins.print" ):
140+ list_models ()
141+
142+ # Verify the URL used was from config
143+ mock_get .assert_called_once ()
144+ call_args = mock_get .call_args [0 ]
145+ assert "https://config-endpoint.com:4142/v1/models" in call_args [0 ]
146+
147+ @patch ("code_assistant_manager.litellm_models.requests.get" )
148+ @patch .dict (os .environ , {"API_KEY_LITELLM" : "test-key" , "endpoint" : "https://env-endpoint.com:4142" })
149+ def test_endpoint_url_from_environment (self , mock_get ):
150+ """Test that endpoint URL prioritizes environment variable over config."""
151+ mock_response = MagicMock ()
152+ mock_response .json .return_value = {"data" : [{"id" : "gpt-4" }]}
153+ mock_get .return_value = mock_response
154+
155+ from code_assistant_manager .litellm_models import list_models
156+
157+ # Mock ConfigManager - this should not be used for endpoint URL
158+ with patch ("code_assistant_manager.litellm_models.ConfigManager" ) as mock_config_class :
159+ mock_config = MagicMock ()
160+ mock_config_class .return_value = mock_config
161+
162+ # Mock stdout to capture output
163+ with patch ("builtins.print" ):
164+ list_models ()
165+
166+ # Verify the URL used was from environment
167+ mock_get .assert_called_once ()
168+ call_args = mock_get .call_args [0 ]
169+ assert "https://env-endpoint.com:4142/v1/models" in call_args [0 ]
0 commit comments