1+ import os
2+ import sys
3+ from unittest .mock import AsyncMock , MagicMock , patch
4+
5+ import pytest
6+
7+ sys .path .insert (
8+ 0 , os .path .abspath ("../../.." )
9+ ) # Adds the parent directory to the system path
10+
11+ from litellm .proxy .pass_through_endpoints .llm_passthrough_endpoints import (
12+ anthropic_proxy_route ,
13+ )
14+
15+
16+ class TestAnthropicAuthHeaders :
17+ """Test authentication header handling in anthropic_proxy_route."""
18+
19+ @pytest .fixture
20+ def mock_request (self ):
21+ """Create a mock request object."""
22+ request = MagicMock ()
23+ request .method = "POST"
24+ request .headers = {}
25+ return request
26+
27+ @pytest .fixture
28+ def mock_response (self ):
29+ """Create a mock FastAPI response object."""
30+ return MagicMock ()
31+
32+ @pytest .fixture
33+ def mock_user_api_key_dict (self ):
34+ """Create a mock user API key dict."""
35+ return {"user_id" : "test_user" }
36+
37+ @pytest .mark .asyncio
38+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route" )
39+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.is_streaming_request_fn" )
40+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router" )
41+ async def test_client_authorization_header_priority (
42+ self ,
43+ mock_router ,
44+ mock_streaming ,
45+ mock_create_route ,
46+ mock_request ,
47+ mock_response ,
48+ mock_user_api_key_dict ,
49+ ):
50+ """Test that client Authorization header takes priority over server key."""
51+ # Setup
52+ mock_request .headers = {"authorization" : "Bearer client-key-123" }
53+ mock_router .get_credentials .return_value = "server-key-456"
54+ mock_streaming .return_value = False
55+ mock_endpoint_func = AsyncMock (return_value = "test_response" )
56+ mock_create_route .return_value = mock_endpoint_func
57+
58+ # Act
59+ await anthropic_proxy_route (
60+ endpoint = "v1/messages" ,
61+ request = mock_request ,
62+ fastapi_response = mock_response ,
63+ user_api_key_dict = mock_user_api_key_dict ,
64+ )
65+
66+ # Assert
67+ mock_create_route .assert_called_once ()
68+ call_kwargs = mock_create_route .call_args [1 ]
69+
70+ assert call_kwargs ["custom_headers" ] == {}
71+ assert call_kwargs ["_forward_headers" ] is True
72+
73+ @pytest .mark .asyncio
74+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route" )
75+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.is_streaming_request_fn" )
76+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router" )
77+ async def test_client_x_api_key_header_priority (
78+ self ,
79+ mock_router ,
80+ mock_streaming ,
81+ mock_create_route ,
82+ mock_request ,
83+ mock_response ,
84+ mock_user_api_key_dict ,
85+ ):
86+ """Test that client x-api-key header takes priority over server key."""
87+ # Setup
88+ mock_request .headers = {"x-api-key" : "client-x-api-key-123" }
89+ mock_router .get_credentials .return_value = "server-key-456"
90+ mock_streaming .return_value = False
91+ mock_endpoint_func = AsyncMock (return_value = "test_response" )
92+ mock_create_route .return_value = mock_endpoint_func
93+
94+ # Act
95+ await anthropic_proxy_route (
96+ endpoint = "v1/messages" ,
97+ request = mock_request ,
98+ fastapi_response = mock_response ,
99+ user_api_key_dict = mock_user_api_key_dict ,
100+ )
101+
102+ # Assert
103+ mock_create_route .assert_called_once ()
104+ call_kwargs = mock_create_route .call_args [1 ]
105+
106+ assert call_kwargs ["custom_headers" ] == {}
107+ assert call_kwargs ["_forward_headers" ] is True
108+
109+ @pytest .mark .asyncio
110+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route" )
111+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.is_streaming_request_fn" )
112+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router" )
113+ async def test_server_api_key_fallback (
114+ self ,
115+ mock_router ,
116+ mock_streaming ,
117+ mock_create_route ,
118+ mock_request ,
119+ mock_response ,
120+ mock_user_api_key_dict ,
121+ ):
122+ """Test that server API key is used when no client authentication is provided."""
123+ # Setup
124+ mock_request .headers = {} # No authentication headers
125+ mock_router .get_credentials .return_value = "server-key-456"
126+ mock_streaming .return_value = False
127+ mock_endpoint_func = AsyncMock (return_value = "test_response" )
128+ mock_create_route .return_value = mock_endpoint_func
129+
130+ # Act
131+ await anthropic_proxy_route (
132+ endpoint = "v1/messages" ,
133+ request = mock_request ,
134+ fastapi_response = mock_response ,
135+ user_api_key_dict = mock_user_api_key_dict ,
136+ )
137+
138+ # Assert
139+ mock_create_route .assert_called_once ()
140+ call_kwargs = mock_create_route .call_args [1 ]
141+
142+ assert call_kwargs ["custom_headers" ] == {"x-api-key" : "server-key-456" }
143+ assert call_kwargs ["_forward_headers" ] is True
144+
145+ @pytest .mark .asyncio
146+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route" )
147+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.is_streaming_request_fn" )
148+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router" )
149+ async def test_no_authentication_available (
150+ self ,
151+ mock_router ,
152+ mock_streaming ,
153+ mock_create_route ,
154+ mock_request ,
155+ mock_response ,
156+ mock_user_api_key_dict ,
157+ ):
158+ """Test that no x-api-key header is added when no authentication is available."""
159+ # Setup
160+ mock_request .headers = {} # No authentication headers
161+ mock_router .get_credentials .return_value = None # No server key
162+ mock_streaming .return_value = False
163+ mock_endpoint_func = AsyncMock (return_value = "test_response" )
164+ mock_create_route .return_value = mock_endpoint_func
165+
166+ # Act
167+ await anthropic_proxy_route (
168+ endpoint = "v1/messages" ,
169+ request = mock_request ,
170+ fastapi_response = mock_response ,
171+ user_api_key_dict = mock_user_api_key_dict ,
172+ )
173+
174+ # Assert
175+ mock_create_route .assert_called_once ()
176+ call_kwargs = mock_create_route .call_args [1 ]
177+
178+ assert call_kwargs ["custom_headers" ] == {}
179+ assert call_kwargs ["_forward_headers" ] is True
180+
181+ @pytest .mark .asyncio
182+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route" )
183+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.is_streaming_request_fn" )
184+ @patch ("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router" )
185+ async def test_both_client_headers_present (
186+ self ,
187+ mock_router ,
188+ mock_streaming ,
189+ mock_create_route ,
190+ mock_request ,
191+ mock_response ,
192+ mock_user_api_key_dict ,
193+ ):
194+ """Test that no server key is added when client has both auth headers."""
195+ # Setup
196+ mock_request .headers = {
197+ "authorization" : "Bearer client-auth-key" ,
198+ "x-api-key" : "client-x-api-key"
199+ }
200+ mock_router .get_credentials .return_value = "server-key-456"
201+ mock_streaming .return_value = False
202+ mock_endpoint_func = AsyncMock (return_value = "test_response" )
203+ mock_create_route .return_value = mock_endpoint_func
204+
205+ # Act
206+ await anthropic_proxy_route (
207+ endpoint = "v1/messages" ,
208+ request = mock_request ,
209+ fastapi_response = mock_response ,
210+ user_api_key_dict = mock_user_api_key_dict ,
211+ )
212+
213+ # Assert
214+ mock_create_route .assert_called_once ()
215+ call_kwargs = mock_create_route .call_args [1 ]
216+
217+ assert call_kwargs ["custom_headers" ] == {}
218+ assert call_kwargs ["_forward_headers" ] is True
0 commit comments