@@ -61,7 +61,7 @@ def manage_sync_client_class_state():
61
61
62
62
63
63
@pytest .fixture ()
64
- def test_tool_str_schema (): # Renamed to avoid conflict if original fixture is imported
64
+ def test_tool_str_schema ():
65
65
return ToolSchema (
66
66
description = "Test Tool with String input" ,
67
67
parameters = [
@@ -73,7 +73,7 @@ def test_tool_str_schema(): # Renamed to avoid conflict if original fixture is
73
73
74
74
75
75
@pytest .fixture ()
76
- def test_tool_int_bool_schema (): # Renamed
76
+ def test_tool_int_bool_schema ():
77
77
return ToolSchema (
78
78
description = "Test Tool with Int, Bool" ,
79
79
parameters = [
@@ -84,7 +84,7 @@ def test_tool_int_bool_schema(): # Renamed
84
84
85
85
86
86
@pytest .fixture ()
87
- def test_tool_auth_schema (): # Renamed
87
+ def test_tool_auth_schema ():
88
88
return ToolSchema (
89
89
description = "Test Tool with Int,Bool+Auth" ,
90
90
parameters = [
@@ -104,7 +104,7 @@ def tool_schema_minimal():
104
104
return ToolSchema (description = "Minimal Test Tool" , parameters = [])
105
105
106
106
107
- # --- Helper Functions for Mocking (from existing tests) ---
107
+ # --- Helper Functions for Mocking ---
108
108
def mock_tool_load (
109
109
aio_resp : aioresponses ,
110
110
tool_name : str ,
@@ -113,9 +113,7 @@ def mock_tool_load(
113
113
server_version : str = "0.0.0" ,
114
114
status : int = 200 ,
115
115
callback : Optional [Callable ] = None ,
116
- payload_override : Optional [
117
- Any
118
- ] = None , # Changed from Callable to Any for flexibility
116
+ payload_override : Optional [Any ] = None ,
119
117
):
120
118
url = f"{ base_url } /api/tool/{ tool_name } "
121
119
payload_data = {}
@@ -162,15 +160,9 @@ def mock_tool_invoke(
162
160
def test_sync_client_initialization_and_loop_management ():
163
161
"""Tests that the event loop and thread are managed correctly at class level."""
164
162
client1 = ToolboxSyncClient (TEST_BASE_URL )
165
- assert (
166
- client1 ._ToolboxSyncClient__loop is not None
167
- ), "Loop should be created"
168
- assert (
169
- client1 ._ToolboxSyncClient__thread is not None
170
- ), "Thread should be created"
171
- assert (
172
- client1 ._ToolboxSyncClient__thread .is_alive ()
173
- ), "Thread should be running"
163
+ assert client1 ._ToolboxSyncClient__loop is not None , "Loop should be created"
164
+ assert client1 ._ToolboxSyncClient__thread is not None , "Thread should be created"
165
+ assert client1 ._ToolboxSyncClient__thread .is_alive (), "Thread should be running"
174
166
assert isinstance (
175
167
client1 ._ToolboxSyncClient__async_client , ToolboxClient
176
168
), "Async client should be ToolboxClient instance"
@@ -180,9 +172,7 @@ def test_sync_client_initialization_and_loop_management():
180
172
181
173
client2 = ToolboxSyncClient (TEST_BASE_URL ) # Should reuse existing loop/thread
182
174
assert client2 ._ToolboxSyncClient__loop is loop1 , "Loop should be reused"
183
- assert (
184
- client2 ._ToolboxSyncClient__thread is thread1
185
- ), "Thread should be reused"
175
+ assert client2 ._ToolboxSyncClient__thread is thread1 , "Thread should be reused"
186
176
assert isinstance (client2 ._ToolboxSyncClient__async_client , ToolboxClient )
187
177
188
178
client1 .close () # Closes its async_client's session
@@ -192,22 +182,11 @@ def test_sync_client_initialization_and_loop_management():
192
182
193
183
def test_sync_client_close_method ():
194
184
"""Tests the close() method of ToolboxSyncClient."""
195
- # We need to mock the underlying async_client's close method.
196
- # The __init__ of ToolboxSyncClient creates the async_client in a thread.
197
- # Patching `ToolboxClient` class to control the instance it produces.
198
-
199
- # This specific test has an issue: `async_client.close()` is a coroutine.
200
- # `ToolboxSyncClient.close()` calls `asyncio.run_coroutine_threadsafe(coro, self.__loop).result()`.
201
- # The `self.__loop` in `close()` (and other methods) refers to `ToolboxSyncClient._ToolboxSyncClient__loop`.
202
-
203
185
mock_async_client_instance = AsyncMock (spec = ToolboxClient )
204
186
mock_async_client_instance .close = AsyncMock (
205
187
return_value = None
206
188
) # close() is a coroutine
207
189
208
- # Patch 'create_client' to return our mocked async client instance
209
- # This is tricky because create_client is an inner async function.
210
- # A better way: patch ToolboxClient constructor.
211
190
with patch (
212
191
"toolbox_core.sync_client.ToolboxClient" ,
213
192
return_value = mock_async_client_instance ,
@@ -224,9 +203,6 @@ def test_sync_client_close_method():
224
203
225
204
def test_sync_client_context_manager (aioresponses , tool_schema_minimal ):
226
205
"""Tests the context manager (__enter__ and __exit__) functionality."""
227
- # Use aioresponses to ensure close is effective (though hard to check session state directly)
228
- # The main check is that close() is called on exit.
229
-
230
206
with patch .object (
231
207
ToolboxSyncClient , "close" , wraps = ToolboxSyncClient .close , autospec = True
232
208
) as mock_close_method :
@@ -267,16 +243,13 @@ def test_sync_load_tool_success(aioresponses, test_tool_str_schema):
267
243
test_tool_str_schema .description
268
244
+ f"\n \n Args:\n param1 (str): Description of Param1" # This format comes from ToolboxTool
269
245
)
270
- # This assertion depends on the mock ToolboxTool's __doc__ generation or the real one
271
- # For now, we check that the doc is based on the schema's description
272
246
assert test_tool_str_schema .description in loaded_tool .__doc__
273
247
274
248
sig = inspect .signature (loaded_tool )
275
249
assert list (sig .parameters .keys ()) == [
276
250
p .name for p in test_tool_str_schema .parameters
277
251
]
278
252
279
- # Invoke the synchronous tool
280
253
result = loaded_tool (param1 = "some value" )
281
254
assert result == "sync_tool_ok"
282
255
@@ -351,7 +324,7 @@ def test_sync_load_tool_not_found_in_manifest(aioresponses, test_tool_str_schema
351
324
# The error comes from the underlying async client's parsing
352
325
with pytest .raises (
353
326
ValueError ,
354
- match = f"Tool '{ REQUESTED_TOOL_NAME } ' not found!" , # Adjusted error message if ToolboxClient is source
327
+ match = f"Tool '{ REQUESTED_TOOL_NAME } ' not found!" ,
355
328
):
356
329
client .load_tool (REQUESTED_TOOL_NAME )
357
330
@@ -397,21 +370,15 @@ def post_callback(url, **kwargs):
397
370
def test_sync_add_headers_duplicate_fail (aioresponses ):
398
371
"""Tests that adding a duplicate header via add_headers raises ValueError (from async client)."""
399
372
initial_headers = {"X-Initial-Header" : "initial_value" }
400
-
401
- # The error for duplicate headers is raised by the underlying ToolboxClient.
402
- # We need to ensure ToolboxSyncClient correctly calls and propagates it.
403
- # For this, we can mock the async_client's add_headers.
404
-
405
373
mock_async_client = AsyncMock (spec = ToolboxClient )
406
374
407
375
# Configure add_headers to simulate the ValueError from ToolboxClient
408
376
async def mock_add_headers_async (headers ):
409
377
# Simulate ToolboxClient's check
410
- if "X-Initial-Header" in headers : # simplified check
378
+ if "X-Initial-Header" in headers :
411
379
raise ValueError ("Client header(s) `X-Initial-Header` already registered" )
412
380
413
381
mock_async_client .add_headers = AsyncMock (side_effect = mock_add_headers_async )
414
- # mock_async_client.close = AsyncMock() # For context manager
415
382
416
383
with patch (
417
384
"toolbox_core.sync_client.ToolboxClient" , return_value = mock_async_client
@@ -426,7 +393,7 @@ async def mock_add_headers_async(headers):
426
393
client .add_headers ({"X-Initial-Header" : "another_value" })
427
394
428
395
429
- def test_load_tool_raises_if_loop_or_thread_none (test_tool_str_schema ):
396
+ def test_load_tool_raises_if_loop_or_thread_none ():
430
397
"""
431
398
Tests that load_tool and load_toolset raise ValueError if the class-level
432
399
event loop or thread is None when accessed.
@@ -464,14 +431,13 @@ def test_load_tool_raises_if_loop_or_thread_none(test_tool_str_schema):
464
431
465
432
class TestSyncAuth :
466
433
@pytest .fixture
467
- def expected_header_token (self ): # Renamed to avoid clash
434
+ def expected_header_token (self ):
468
435
return "sync_auth_token_for_testing"
469
436
470
437
@pytest .fixture
471
- def tool_name_auth (self ): # Renamed
438
+ def tool_name_auth (self ):
472
439
return "sync_auth_tool1"
473
440
474
-
475
441
def test_auth_with_load_tool_success (
476
442
self ,
477
443
tool_name_auth ,
@@ -489,7 +455,9 @@ def test_auth_with_load_tool_success(
489
455
)
490
456
491
457
def require_headers_callback (url , ** kwargs ):
492
- assert kwargs ["headers" ].get ("my-auth-service_token" ) == expected_header_token
458
+ assert (
459
+ kwargs ["headers" ].get ("my-auth-service_token" ) == expected_header_token
460
+ )
493
461
return CallbackResult (status = 200 , payload = {"result" : "auth_ok" })
494
462
495
463
aioresponses .post (
@@ -505,7 +473,6 @@ def token_handler():
505
473
tool = client .load_tool (
506
474
tool_name_auth , auth_token_getters = {"my-auth-service" : token_handler }
507
475
)
508
- # Tool invocation depends on parameters; test_tool_auth_schema has argA (int), argB (bool with auth)
509
476
result = tool (argA = 5 ) # argB is the authed param in schema
510
477
assert result == "auth_ok"
511
478
@@ -526,7 +493,9 @@ def test_auth_with_add_token_success(
526
493
)
527
494
528
495
def require_headers_callback (url , ** kwargs ):
529
- assert kwargs ["headers" ].get ("my-auth-service_token" ) == expected_header_token
496
+ assert (
497
+ kwargs ["headers" ].get ("my-auth-service_token" ) == expected_header_token
498
+ )
530
499
return CallbackResult (status = 200 , payload = {"result" : "auth_ok" })
531
500
532
501
aioresponses .post (
@@ -540,7 +509,6 @@ def token_handler():
540
509
return expected_header_token
541
510
542
511
tool = client .load_tool (tool_name_auth )
543
- # Assuming ToolboxSyncTool.add_auth_token_getters works by wrapping async tool's method
544
512
authed_tool = tool .add_auth_token_getters (
545
513
{"my-auth-service" : token_handler }
546
514
)
@@ -558,13 +526,6 @@ def test_auth_with_load_tool_fail_no_token(
558
526
payload = manifest .model_dump (),
559
527
status = 200 ,
560
528
)
561
- # Server will reject if token is missing and required by its logic / schema implies it
562
- # The require_headers_callback in this class's fixture handles the 400
563
- # For this test, the error should be raised during tool invocation if mock Tool requires it
564
- # Or, if the server itself validates based on schema, the POST might fail.
565
- # The mock ToolboxTool's __call__ has a simplified auth check.
566
-
567
- # If the server responds with 400 due to missing token in payload
568
529
aioresponses .post (
569
530
f"{ TEST_BASE_URL } /api/tool/{ tool_name_auth } /invoke" ,
570
531
payload = {"error" : "Missing token" },
@@ -575,8 +536,9 @@ def test_auth_with_load_tool_fail_no_token(
575
536
tool = client .load_tool (tool_name_auth )
576
537
# Invocation should fail
577
538
with pytest .raises (
578
- ValueError , match = "One or more of the following authn services are required to invoke this tool: my-auth-service"
579
- ): # Match error from server
539
+ ValueError ,
540
+ match = "One or more of the following authn services are required to invoke this tool: my-auth-service" ,
541
+ ): # Match error from client
580
542
tool (argA = 15 , argB = True )
581
543
582
544
def test_add_auth_token_getters_duplicate_fail (
@@ -598,30 +560,10 @@ def test_add_auth_token_getters_duplicate_fail(
598
560
599
561
# First addition should work
600
562
authed_tool = tool .add_auth_token_getters ({AUTH_SERVICE : lambda : "token1" })
601
- # The error for duplicate registration is typically raised by the underlying async tool/client logic
602
- # My mock ToolboxTool.add_auth_token_getters currently overwrites.
603
- # The original test for ToolboxClient implies `_ToolboxTool__auth_service_token_getters`
604
- # The check should be in ToolboxTool's add_auth_token_getters.
605
- # For ToolboxSyncClient, the error would propagate if ToolboxTool raises it.
606
- # Let's assume ToolboxTool is robust.
607
- # If the original test `TestAuth.test_add_auth_token_getters_duplicate_fail` passed,
608
- # it's because `ToolboxTool.add_auth_token_getters` raised that ValueError.
609
- # My mock `ToolboxTool` needs to be updated or the test adjusted.
610
- # For now, assuming it propagates:
611
563
with pytest .raises (
612
564
ValueError ,
613
565
match = f"Authentication source\\ (s\\ ) `{ AUTH_SERVICE } ` already registered in tool `{ tool_name_auth } `." ,
614
566
):
615
- # This error message implies the state is checked *before* adding.
616
- # The mock ToolboxTool's add_auth_token_getters would need to check self._auth_getters.
617
- # This test may need a more sophisticated mock for ToolboxTool or it tests ToolboxClient behavior.
618
- # Given the prompt, this is testing the wrapper. If the error originates from the wrapped object, it should propagate.
619
- # The error message is from the original `ToolboxClient` test suite.
620
- # This test would pass if the *async* `ToolboxTool.add_auth_token_getters`
621
- # has the logic to raise this error.
622
- # For the sync wrapper, we're testing that it correctly calls the async method
623
- # and re-wraps, and propagates exceptions.
624
- # Let's assume the async tool raises it.
625
567
authed_tool .add_auth_token_getters ({AUTH_SERVICE : lambda : "token2" })
626
568
627
569
def test_add_auth_token_getters_missing_fail (
@@ -642,7 +584,6 @@ def test_add_auth_token_getters_missing_fail(
642
584
643
585
with pytest .raises (
644
586
ValueError ,
645
- # This error comes from validation logic within ToolboxTool or ToolboxClient upon tool creation/configuration
646
587
match = f"Authentication source\\ (s\\ ) `{ UNUSED_AUTH_SERVICE } ` unused by tool `{ tool_name_auth } `." ,
647
588
):
648
589
tool .add_auth_token_getters ({UNUSED_AUTH_SERVICE : lambda : "token" })
@@ -665,7 +606,6 @@ def test_constructor_getters_missing_fail(
665
606
ValueError ,
666
607
match = f"Validation failed for tool '{ tool_name_auth } ': unused auth tokens: { UNUSED_AUTH_SERVICE } ." ,
667
608
):
668
- # This validation happens in ToolboxClient.load_tool based on schema vs getters
669
609
client .load_tool (
670
610
tool_name_auth ,
671
611
auth_token_getters = {UNUSED_AUTH_SERVICE : lambda : "token" },
0 commit comments