Skip to content

Commit d3dee2e

Browse files
author
Koman Rudden
committed
feat: enhance token injection handling in API calls
1 parent 6e9adeb commit d3dee2e

2 files changed

Lines changed: 122 additions & 13 deletions

File tree

kinde_sdk/management/management_client.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,21 +130,39 @@ def call_api_with_token(*args, **kwargs):
130130
"Failed to acquire access token: token manager returned an invalid token"
131131
)
132132

133+
args_list = list(args)
134+
header_params = None
135+
header_params_in_args = False
136+
137+
# Check if header_params was passed positionally (index 2)
138+
if len(args_list) > 2:
139+
header_params = args_list[2]
140+
header_params_in_args = True
141+
# Check if header_params was passed as keyword argument
142+
elif 'header_params' in kwargs:
143+
header_params = kwargs['header_params']
144+
133145
# Initialize header_params if not present
134-
if 'header_params' not in kwargs:
135-
kwargs['header_params'] = {}
146+
if header_params is None:
147+
header_params = {}
136148

137149
# Check for existing Authorization header and handle appropriately
138-
if 'Authorization' in kwargs['header_params']:
139-
existing_auth = kwargs['header_params']['Authorization']
150+
if 'Authorization' in header_params:
140151
logger.warning(
141-
f"Overwriting existing Authorization header. "
142-
f"Existing value: {existing_auth[:20]}... "
143-
f"New value: Bearer {token[:20]}..."
152+
"Overwriting existing Authorization header for ManagementClient request."
144153
)
145154

146155
# Inject the token into headers
147-
kwargs['header_params']['Authorization'] = f"Bearer {token}"
156+
header_params['Authorization'] = f"Bearer {token}"
157+
158+
# Put header_params back in the correct location
159+
if header_params_in_args:
160+
# Replace the positional argument
161+
args_list[2] = header_params
162+
args = tuple(args_list)
163+
else:
164+
# Set as keyword argument
165+
kwargs['header_params'] = header_params
148166

149167
# Call the original method
150168
return original_call_api(*args, **kwargs)

testv2/testv2_management/test_management_client.py

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,97 @@ def test_token_injection_warns_on_existing_auth_header(self, mock_token_manager_
271271
call_kwargs = original_call_api.call_args[1]
272272
assert call_kwargs['header_params']['Authorization'] == f"Bearer {test_token}"
273273

274+
@patch('kinde_sdk.management.management_client.Configuration')
275+
@patch('kinde_sdk.management.management_client.ApiClient')
276+
@patch('kinde_sdk.management.management_client.ManagementTokenManager')
277+
def test_token_injection_with_positional_header_params(self, mock_token_manager_class, mock_api_client_class, mock_config_class):
278+
"""Test that token injection works when header_params is passed positionally."""
279+
# Setup mocks
280+
mock_token_manager_class.return_value = self.mock_token_manager
281+
mock_api_client_instance = Mock()
282+
mock_api_client_class.return_value = mock_api_client_instance
283+
mock_config_class.return_value = self.mock_configuration
284+
285+
# Mock token
286+
test_token = "test_access_token"
287+
self.mock_token_manager.get_access_token.return_value = test_token
288+
289+
# Mock the original call_api method
290+
original_call_api = Mock(return_value="api_response")
291+
mock_api_client_instance.call_api = original_call_api
292+
293+
# Create client - this wraps call_api with token injection
294+
client = ManagementClient(self.domain, self.client_id, self.client_secret)
295+
296+
# Call with header_params passed positionally (3rd argument after method, url)
297+
# Signature: call_api(self, method, url, header_params=None, ...)
298+
existing_headers = {'X-Custom': 'value'}
299+
client.api_client.call_api(
300+
'GET',
301+
'https://test.kinde.com/api/v1/users',
302+
existing_headers # Passed positionally as 3rd argument
303+
)
304+
305+
# Verify token was requested from token manager
306+
self.mock_token_manager.get_access_token.assert_called()
307+
308+
# Verify the wrapped call_api was called
309+
original_call_api.assert_called_once()
310+
311+
# Get the actual call arguments
312+
call_args = original_call_api.call_args[0]
313+
314+
# Verify Authorization header was added to the positional header_params
315+
assert len(call_args) >= 3
316+
header_params_arg = call_args[2]
317+
assert header_params_arg is not None
318+
assert 'Authorization' in header_params_arg
319+
assert header_params_arg['Authorization'] == f"Bearer {test_token}"
320+
# Verify original header is preserved
321+
assert header_params_arg['X-Custom'] == 'value'
322+
323+
@patch('kinde_sdk.management.management_client.Configuration')
324+
@patch('kinde_sdk.management.management_client.ApiClient')
325+
@patch('kinde_sdk.management.management_client.ManagementTokenManager')
326+
def test_token_injection_with_no_header_params(self, mock_token_manager_class, mock_api_client_class, mock_config_class):
327+
"""Test that token injection works when header_params is not provided at all."""
328+
# Setup mocks
329+
mock_token_manager_class.return_value = self.mock_token_manager
330+
mock_api_client_instance = Mock()
331+
mock_api_client_class.return_value = mock_api_client_instance
332+
mock_config_class.return_value = self.mock_configuration
333+
334+
# Mock token
335+
test_token = "test_access_token"
336+
self.mock_token_manager.get_access_token.return_value = test_token
337+
338+
# Mock the original call_api method
339+
original_call_api = Mock(return_value="api_response")
340+
mock_api_client_instance.call_api = original_call_api
341+
342+
# Create client - this wraps call_api with token injection
343+
client = ManagementClient(self.domain, self.client_id, self.client_secret)
344+
345+
# Call without any header_params (neither positional nor keyword)
346+
client.api_client.call_api(
347+
'GET',
348+
'https://test.kinde.com/api/v1/users'
349+
)
350+
351+
# Verify token was requested from token manager
352+
self.mock_token_manager.get_access_token.assert_called()
353+
354+
# Verify the wrapped call_api was called
355+
original_call_api.assert_called_once()
356+
357+
# Get the call kwargs
358+
call_kwargs = original_call_api.call_args[1]
359+
360+
# Verify Authorization header was added as keyword argument
361+
assert 'header_params' in call_kwargs
362+
assert 'Authorization' in call_kwargs['header_params']
363+
assert call_kwargs['header_params']['Authorization'] == f"Bearer {test_token}"
364+
274365
@patch('kinde_sdk.management.management_client.Configuration')
275366
@patch('kinde_sdk.management.management_client.ApiClient')
276367
@patch('kinde_sdk.management.management_client.ManagementTokenManager')
@@ -918,7 +1009,7 @@ def test_api_initialization_fails_with_no_apis_found(self, mock_getmembers, mock
9181009

9191010
# Creating client should raise RuntimeError with descriptive message
9201011
with pytest.raises(RuntimeError) as exc_info:
921-
client = ManagementClient(self.domain, self.client_id, self.client_secret)
1012+
ManagementClient(self.domain, self.client_id, self.client_secret)
9221013

9231014
# Verify the error message is descriptive
9241015
assert "No API classes found" in str(exc_info.value)
@@ -938,15 +1029,15 @@ def test_api_initialization_handles_instantiation_failure(self, mock_getmembers,
9381029

9391030
# Create a mock API class that raises an exception when instantiated
9401031
class BrokenApi:
941-
def __init__(self, **kwargs):
1032+
def __init__(self, **_):
9421033
raise ValueError("Missing required parameter")
9431034

9441035
# Mock inspect.getmembers to return our broken API class
9451036
mock_getmembers.return_value = [('BrokenApi', BrokenApi)]
9461037

9471038
# Creating client should raise RuntimeError with descriptive message
9481039
with pytest.raises(RuntimeError) as exc_info:
949-
client = ManagementClient(self.domain, self.client_id, self.client_secret)
1040+
ManagementClient(self.domain, self.client_id, self.client_secret)
9501041

9511042
# Verify the error message is descriptive
9521043
assert "Failed to initialize API class BrokenApi" in str(exc_info.value)
@@ -992,8 +1083,8 @@ def __init__(self, **kwargs):
9921083
client._initialize_api_classes()
9931084

9941085
# Verify warning was logged about the collision
995-
warning_calls = [call for call in mock_logger.warning.call_args_list
996-
if 'test_collision_api' in str(call)]
1086+
warning_calls = [warning_call for warning_call in mock_logger.warning.call_args_list
1087+
if 'test_collision_api' in str(warning_call)]
9971088
assert len(warning_calls) > 0, "Should log warning about attribute collision"
9981089

9991090
# Verify the original value wasn't overwritten

0 commit comments

Comments
 (0)