@@ -250,3 +250,277 @@ async fn test_disabled_cache_behavior() {
250250 let stats = bridge. cache_stats ( ) . await ;
251251 assert_eq ! ( stats. size, 0 ) ;
252252}
253+
254+ /// Tests connection limit enforcement when limit is reached
255+ #[ tokio:: test]
256+ async fn test_connection_limit_reached ( ) {
257+ let bridge = Bridge :: with_limits ( 100 , 1 ) ; // Max 1 connection
258+
259+ // First connection should be allowed (but will fail without valid command)
260+ let server1 = ServerId :: new ( "server1" ) ;
261+ let _result1 = bridge. connect ( server1. clone ( ) , "nonexistent-cmd" ) . await ;
262+
263+ // Should either succeed in attempting connection or fail due to invalid command
264+ // Either way, connection limit logic is tested
265+ let ( current, max) = bridge. connection_limits ( ) . await ;
266+ assert_eq ! ( max, 1 ) ;
267+
268+ // Verify the limit value is enforced
269+ assert ! ( current <= max) ;
270+ }
271+
272+ /// Tests empty server_id handling
273+ #[ tokio:: test]
274+ async fn test_empty_server_id ( ) {
275+ let bridge = Bridge :: new ( 100 ) ;
276+ let server_id = ServerId :: new ( "" ) ;
277+
278+ // Should handle empty server IDs gracefully
279+ let result = bridge. connect ( server_id. clone ( ) , "test-cmd" ) . await ;
280+ assert ! ( result. is_err( ) ) ;
281+ }
282+
283+ /// Tests connection with whitespace in command
284+ #[ tokio:: test]
285+ async fn test_command_with_whitespace ( ) {
286+ let bridge = Bridge :: new ( 100 ) ;
287+ let server_id = ServerId :: new ( "test" ) ;
288+
289+ // Command with spaces should be validated
290+ let result = bridge. connect ( server_id, "test cmd with spaces" ) . await ;
291+
292+ // Should either fail validation or connection attempt
293+ assert ! ( result. is_err( ) ) ;
294+ }
295+
296+ /// Tests disconnect followed by connection count check
297+ #[ tokio:: test]
298+ async fn test_disconnect_updates_count ( ) {
299+ let bridge = Bridge :: new ( 100 ) ;
300+ let server_id = ServerId :: new ( "test" ) ;
301+
302+ // Initial count should be 0
303+ assert_eq ! ( bridge. connection_count( ) . await , 0 ) ;
304+
305+ // Disconnect non-existent (should not affect count)
306+ bridge. disconnect ( & server_id) . await ;
307+ assert_eq ! ( bridge. connection_count( ) . await , 0 ) ;
308+ }
309+
310+ /// Tests connection limits after disconnect
311+ #[ tokio:: test]
312+ async fn test_connection_limits_after_disconnect ( ) {
313+ let bridge = Bridge :: with_limits ( 100 , 5 ) ;
314+ let server_id = ServerId :: new ( "test" ) ;
315+
316+ let ( current, max) = bridge. connection_limits ( ) . await ;
317+ assert_eq ! ( current, 0 ) ;
318+ assert_eq ! ( max, 5 ) ;
319+
320+ // Disconnect (no connection exists)
321+ bridge. disconnect ( & server_id) . await ;
322+
323+ let ( current2, max2) = bridge. connection_limits ( ) . await ;
324+ assert_eq ! ( current2, 0 ) ;
325+ assert_eq ! ( max2, 5 ) ;
326+ }
327+
328+ /// Tests call_tool error when server never connected
329+ #[ tokio:: test]
330+ async fn test_call_tool_server_never_connected ( ) {
331+ let bridge = Bridge :: new ( 100 ) ;
332+ let server_id = ServerId :: new ( "never-connected" ) ;
333+ let tool_name = ToolName :: new ( "some_tool" ) ;
334+ let params = json ! ( { "key" : "value" } ) ;
335+
336+ let result = bridge. call_tool ( & server_id, & tool_name, params) . await ;
337+
338+ assert ! ( result. is_err( ) ) ;
339+ match result {
340+ Err ( Error :: ConnectionFailed { server, source } ) => {
341+ assert_eq ! ( server, "never-connected" ) ;
342+ assert ! ( source. to_string( ) . contains( "not connected" ) ) ;
343+ }
344+ _ => panic ! ( "Expected ConnectionFailed error" ) ,
345+ }
346+ }
347+
348+ /// Tests cache key generation with different parameters
349+ #[ tokio:: test]
350+ async fn test_cache_with_different_params ( ) {
351+ let bridge = Bridge :: new ( 100 ) ;
352+ let server_id = ServerId :: new ( "test-server" ) ;
353+ let tool_name = ToolName :: new ( "test-tool" ) ;
354+
355+ // These should generate different cache keys
356+ let params1 = json ! ( { "a" : 1 , "b" : 2 } ) ;
357+ let params2 = json ! ( { "a" : 2 , "b" : 1 } ) ;
358+
359+ // Both will fail (no connection), but cache key logic is tested
360+ let result1 = bridge. call_tool ( & server_id, & tool_name, params1) . await ;
361+ let result2 = bridge. call_tool ( & server_id, & tool_name, params2. clone ( ) ) . await ;
362+
363+ assert ! ( result1. is_err( ) ) ;
364+ assert ! ( result2. is_err( ) ) ;
365+
366+ // Verify cache stats remain at 0 (no successful calls to cache)
367+ let stats = bridge. cache_stats ( ) . await ;
368+ assert_eq ! ( stats. size, 0 ) ;
369+ }
370+
371+ /// Tests with_limits constructor with various configurations
372+ #[ test]
373+ fn test_with_limits_configurations ( ) {
374+ // Minimum viable configuration
375+ let bridge1 = Bridge :: with_limits ( 1 , 1 ) ;
376+ let _ = bridge1;
377+
378+ // Large configuration
379+ let bridge2 = Bridge :: with_limits ( 1_000_000 , 1000 ) ;
380+ let _ = bridge2;
381+
382+ // Asymmetric configuration (small cache, many connections)
383+ let bridge3 = Bridge :: with_limits ( 10 , 100 ) ;
384+ let _ = bridge3;
385+ }
386+
387+ /// Tests cache_stats with various cache states
388+ #[ tokio:: test]
389+ async fn test_cache_stats_edge_cases ( ) {
390+ // Minimum cache size
391+ let bridge = Bridge :: new ( 1 ) ;
392+ let stats = bridge. cache_stats ( ) . await ;
393+ assert_eq ! ( stats. capacity, 1 ) ;
394+ assert_eq ! ( stats. size, 0 ) ;
395+
396+ // Very large cache
397+ let bridge_large = Bridge :: new ( 1_000_000 ) ;
398+ let stats_large = bridge_large. cache_stats ( ) . await ;
399+ assert_eq ! ( stats_large. capacity, 1_000_000 ) ;
400+ assert_eq ! ( stats_large. size, 0 ) ;
401+ }
402+
403+ /// Tests connection_call_count with various server IDs
404+ #[ tokio:: test]
405+ async fn test_connection_call_count_variations ( ) {
406+ let bridge = Bridge :: new ( 100 ) ;
407+
408+ // Non-existent server
409+ let server1 = ServerId :: new ( "nonexistent" ) ;
410+ assert ! ( bridge. connection_call_count( & server1) . await . is_none( ) ) ;
411+
412+ // Different server ID
413+ let server2 = ServerId :: new ( "another-server" ) ;
414+ assert ! ( bridge. connection_call_count( & server2) . await . is_none( ) ) ;
415+
416+ // Empty string server ID
417+ let server3 = ServerId :: new ( "" ) ;
418+ assert ! ( bridge. connection_call_count( & server3) . await . is_none( ) ) ;
419+ }
420+
421+ /// Tests concurrent disconnect operations
422+ #[ tokio:: test]
423+ async fn test_concurrent_disconnects ( ) {
424+ use std:: sync:: Arc ;
425+
426+ let bridge = Arc :: new ( Bridge :: new ( 100 ) ) ;
427+ let server_id = ServerId :: new ( "test" ) ;
428+
429+ let mut handles = vec ! [ ] ;
430+
431+ // Spawn multiple tasks disconnecting concurrently
432+ for _ in 0 ..10 {
433+ let bridge_clone = Arc :: clone ( & bridge) ;
434+ let server_clone = server_id. clone ( ) ;
435+ let handle = tokio:: spawn ( async move {
436+ bridge_clone. disconnect ( & server_clone) . await ;
437+ } ) ;
438+ handles. push ( handle) ;
439+ }
440+
441+ // Wait for all tasks
442+ for handle in handles {
443+ handle. await . unwrap ( ) ;
444+ }
445+
446+ // Should remain at 0 connections
447+ assert_eq ! ( bridge. connection_count( ) . await , 0 ) ;
448+ }
449+
450+ /// Tests CacheStats Debug implementation
451+ #[ test]
452+ fn test_cache_stats_debug ( ) {
453+ let stats = CacheStats {
454+ size : 42 ,
455+ capacity : 100 ,
456+ } ;
457+
458+ let debug_str = format ! ( "{:?}" , stats) ;
459+ assert ! ( debug_str. contains( "42" ) ) ;
460+ assert ! ( debug_str. contains( "100" ) ) ;
461+ }
462+
463+ /// Tests Bridge Debug implementation
464+ #[ test]
465+ fn test_bridge_debug ( ) {
466+ let bridge = Bridge :: new ( 100 ) ;
467+ let debug_str = format ! ( "{:?}" , bridge) ;
468+
469+ assert ! ( debug_str. contains( "Bridge" ) ) ;
470+ }
471+
472+ /// Tests command validation with special characters
473+ #[ tokio:: test]
474+ async fn test_command_validation_special_chars ( ) {
475+ let bridge = Bridge :: new ( 100 ) ;
476+ let server_id = ServerId :: new ( "test" ) ;
477+
478+ // Commands with special characters that might indicate injection attempts
479+ let dangerous_commands = vec ! [
480+ "cmd && malicious" ,
481+ "cmd || fallback" ,
482+ "cmd | pipe" ,
483+ "cmd > redirect" ,
484+ "cmd < input" ,
485+ "cmd `backtick`" ,
486+ "cmd $(substitution)" ,
487+ "cmd;chain" ,
488+ ] ;
489+
490+ for cmd in dangerous_commands {
491+ let result = bridge. connect ( server_id. clone ( ) , cmd) . await ;
492+ // Should fail validation or connection
493+ assert ! ( result. is_err( ) , "Command should be rejected: {}" , cmd) ;
494+ }
495+ }
496+
497+ /// Tests connection limits percentage calculation
498+ #[ tokio:: test]
499+ #[ allow( clippy:: cast_precision_loss) ]
500+ async fn test_connection_usage_percentage ( ) {
501+ let bridge = Bridge :: with_limits ( 100 , 10 ) ;
502+
503+ let ( current, max) = bridge. connection_limits ( ) . await ;
504+ let usage_percent = ( current as f64 / max as f64 ) * 100.0 ;
505+
506+ assert_eq ! ( usage_percent, 0.0 ) ;
507+ assert_eq ! ( max, 10 ) ;
508+ }
509+
510+ /// Tests cache enable/disable state persistence
511+ #[ test]
512+ fn test_cache_state_persistence ( ) {
513+ let mut bridge = Bridge :: new ( 100 ) ;
514+
515+ // Initial state
516+ assert ! ( format!( "{:?}" , bridge) . len( ) > 0 ) ;
517+
518+ // Toggle state multiple times
519+ bridge. disable_cache ( ) ;
520+ bridge. disable_cache ( ) ; // Double disable
521+ bridge. enable_cache ( ) ;
522+ bridge. enable_cache ( ) ; // Double enable
523+
524+ // Should be stable
525+ assert ! ( format!( "{:?}" , bridge) . len( ) > 0 ) ;
526+ }
0 commit comments