@@ -474,17 +474,24 @@ def test_add_all_servers_uses_thread_pool(self, client, sample_config):
474474 with patch (
475475 "code_assistant_manager.mcp.base_client.ThreadPoolExecutor"
476476 ) as mock_executor :
477- mock_future = MagicMock ()
478- mock_future .result .return_value = True
479- mock_executor .return_value .__enter__ .return_value .submit .return_value = (
480- mock_future
481- )
482- mock_executor .return_value .__enter__ .return_value .as_completed .return_value = [
483- mock_future ,
484- mock_future ,
477+ # Create mock futures
478+ mock_future1 = MagicMock ()
479+ mock_future1 .result .return_value = True
480+ mock_future2 = MagicMock ()
481+ mock_future2 .result .return_value = True
482+
483+ # Make submit return different futures
484+ mock_executor .return_value .__enter__ .return_value .submit .side_effect = [
485+ mock_future1 ,
486+ mock_future2 ,
485487 ]
486488
487- result = client .add_all_servers ()
489+ # Patch as_completed at module level (where it's imported)
490+ with patch (
491+ "code_assistant_manager.mcp.base_client.as_completed" ,
492+ return_value = [mock_future1 , mock_future2 ],
493+ ):
494+ result = client .add_all_servers ()
488495
489496 assert mock_executor .called
490497 assert result is True
@@ -497,17 +504,24 @@ def test_remove_all_servers_uses_thread_pool(self, client, sample_config):
497504 with patch (
498505 "code_assistant_manager.mcp.base_client.ThreadPoolExecutor"
499506 ) as mock_executor :
500- mock_future = MagicMock ()
501- mock_future .result .return_value = True
502- mock_executor .return_value .__enter__ .return_value .submit .return_value = (
503- mock_future
504- )
505- mock_executor .return_value .__enter__ .return_value .as_completed .return_value = [
506- mock_future ,
507- mock_future ,
507+ # Create mock futures
508+ mock_future1 = MagicMock ()
509+ mock_future1 .result .return_value = True
510+ mock_future2 = MagicMock ()
511+ mock_future2 .result .return_value = True
512+
513+ # Make submit return different futures
514+ mock_executor .return_value .__enter__ .return_value .submit .side_effect = [
515+ mock_future1 ,
516+ mock_future2 ,
508517 ]
509518
510- result = client .remove_all_servers ()
519+ # Patch as_completed at module level (where it's imported)
520+ with patch (
521+ "code_assistant_manager.mcp.base_client.as_completed" ,
522+ return_value = [mock_future1 , mock_future2 ],
523+ ):
524+ result = client .remove_all_servers ()
511525
512526 assert mock_executor .called
513527 assert result is True
@@ -516,26 +530,29 @@ def test_remove_all_servers_uses_thread_pool(self, client, sample_config):
516530class TestMCPClientErrorHandling :
517531 """Test error handling in MCPClient."""
518532
519- def test_operations_handle_exceptions_gracefully (self , client ):
520- """Test that operations handle exceptions without crashing ."""
533+ def test_operations_return_false_for_missing_servers (self , client , sample_config ):
534+ """Test that operations return False for nonexistent servers ."""
521535 with patch .object (
522- client , "get_tool_config" , side_effect = Exception ( "Config error" )
536+ client , "get_tool_config" , return_value = sample_config [ "servers" ]
523537 ):
524- # These should not raise exceptions
525- result_add = client .add_server ("test" )
526- result_remove = client .remove_server ("test" )
527- result_list = client .list_servers ()
528- result_add_all = client .add_all_servers ()
529- result_remove_all = client .remove_all_servers ()
530- result_refresh = client .refresh_servers ()
531-
532- # All should return False (failure) rather than crashing
538+ # Server not in config should return False
539+ result_add = client .add_server ("nonexistent" )
540+ result_remove = client .remove_server ("nonexistent" )
541+
533542 assert result_add is False
534543 assert result_remove is False
535- assert result_list is False
536- assert result_add_all is False
537- assert result_remove_all is False
538- assert result_refresh is False
544+
545+ def test_add_all_servers_returns_false_when_no_servers (self , client ):
546+ """Test add_all_servers returns False when no servers configured."""
547+ with patch .object (client , "get_tool_config" , return_value = {}):
548+ result = client .add_all_servers ()
549+ assert result is False
550+
551+ def test_remove_all_servers_returns_false_when_no_servers (self , client ):
552+ """Test remove_all_servers returns False when no servers configured."""
553+ with patch .object (client , "get_tool_config" , return_value = {}):
554+ result = client .remove_all_servers ()
555+ assert result is False
539556
540557 def test_fallback_operations_handle_json_errors (self , client , tmp_path ):
541558 """Test fallback operations handle JSON parsing errors."""
@@ -550,3 +567,195 @@ def test_fallback_operations_handle_json_errors(self, client, tmp_path):
550567 # Should handle errors gracefully
551568 assert result_add is False
552569 assert result_remove is False
570+
571+
572+ class TestConfigFileHelpers :
573+ """Test module-level config file helper functions."""
574+
575+ def test_load_config_file_json (self , tmp_path ):
576+ """Test loading a JSON config file."""
577+ from code_assistant_manager .mcp .base_client import _load_config_file
578+
579+ config_path = tmp_path / "test.json"
580+ config_data = {"mcpServers" : {"test" : {"type" : "stdio" }}}
581+ with open (config_path , "w" ) as f :
582+ json .dump (config_data , f )
583+
584+ config , is_toml = _load_config_file (config_path )
585+
586+ assert config == config_data
587+ assert is_toml is False
588+
589+ def test_load_config_file_nonexistent (self , tmp_path ):
590+ """Test loading a nonexistent config file returns empty dict."""
591+ from code_assistant_manager .mcp .base_client import _load_config_file
592+
593+ config_path = tmp_path / "nonexistent.json"
594+
595+ config , is_toml = _load_config_file (config_path )
596+
597+ assert config == {}
598+ assert is_toml is False
599+
600+ def test_load_config_file_invalid_json (self , tmp_path ):
601+ """Test loading invalid JSON returns None."""
602+ from code_assistant_manager .mcp .base_client import _load_config_file
603+
604+ config_path = tmp_path / "invalid.json"
605+ with open (config_path , "w" ) as f :
606+ f .write ("{ invalid json }" )
607+
608+ config , is_toml = _load_config_file (config_path )
609+
610+ assert config is None
611+ assert is_toml is False
612+
613+ def test_save_config_file_json (self , tmp_path ):
614+ """Test saving a JSON config file."""
615+ from code_assistant_manager .mcp .base_client import _save_config_file
616+
617+ config_path = tmp_path / "test.json"
618+ config_data = {"mcpServers" : {"test" : {"type" : "stdio" }}}
619+
620+ result = _save_config_file (config_path , config_data , is_toml = False )
621+
622+ assert result is True
623+ assert config_path .exists ()
624+ with open (config_path , "r" ) as f :
625+ saved_config = json .load (f )
626+ assert saved_config == config_data
627+
628+ def test_save_config_file_creates_parent_dirs (self , tmp_path ):
629+ """Test that save creates parent directories if needed."""
630+ from code_assistant_manager .mcp .base_client import _save_config_file
631+
632+ config_path = tmp_path / "subdir" / "nested" / "test.json"
633+ config_data = {"key" : "value" }
634+
635+ result = _save_config_file (config_path , config_data , is_toml = False )
636+
637+ assert result is True
638+ assert config_path .exists ()
639+
640+ def test_server_exists_in_config_mcpServers (self ):
641+ """Test checking server existence in mcpServers structure."""
642+ from code_assistant_manager .mcp .base_client import _server_exists_in_config
643+
644+ config = {"mcpServers" : {"test_server" : {"type" : "stdio" }}}
645+
646+ assert _server_exists_in_config (config , "test_server" ) is True
647+ assert _server_exists_in_config (config , "nonexistent" ) is False
648+
649+ def test_server_exists_in_config_servers (self ):
650+ """Test checking server existence in servers structure."""
651+ from code_assistant_manager .mcp .base_client import _server_exists_in_config
652+
653+ config = {"servers" : {"test_server" : {"type" : "http" }}}
654+
655+ assert _server_exists_in_config (config , "test_server" ) is True
656+ assert _server_exists_in_config (config , "nonexistent" ) is False
657+
658+ def test_server_exists_in_config_direct (self ):
659+ """Test checking server existence in direct structure."""
660+ from code_assistant_manager .mcp .base_client import _server_exists_in_config
661+
662+ config = {"test_server" : {"type" : "stdio" }}
663+
664+ assert _server_exists_in_config (config , "test_server" ) is True
665+ assert _server_exists_in_config (config , "nonexistent" ) is False
666+
667+ def test_get_preferred_container_key_existing (self ):
668+ """Test getting preferred container key when one exists."""
669+ from code_assistant_manager .mcp .base_client import _get_preferred_container_key
670+
671+ config = {"servers" : {}}
672+
673+ result = _get_preferred_container_key (config , is_toml = False )
674+
675+ assert result == "servers"
676+
677+ def test_get_preferred_container_key_default_json (self ):
678+ """Test default container key for JSON files."""
679+ from code_assistant_manager .mcp .base_client import _get_preferred_container_key
680+
681+ config = {}
682+
683+ result = _get_preferred_container_key (config , is_toml = False )
684+
685+ assert result == "mcpServers"
686+
687+ def test_get_preferred_container_key_default_toml (self ):
688+ """Test default container key for TOML files."""
689+ from code_assistant_manager .mcp .base_client import _get_preferred_container_key
690+
691+ config = {}
692+
693+ result = _get_preferred_container_key (config , is_toml = True )
694+
695+ assert result == "mcp_servers"
696+
697+ def test_remove_server_from_containers_mcpServers (self ):
698+ """Test removing server from mcpServers container."""
699+ from code_assistant_manager .mcp .base_client import (
700+ _remove_server_from_containers ,
701+ )
702+
703+ config = {"mcpServers" : {"server1" : {}, "server2" : {}}}
704+
705+ result = _remove_server_from_containers (config , "server1" )
706+
707+ assert result is True
708+ assert "server1" not in config ["mcpServers" ]
709+ assert "server2" in config ["mcpServers" ]
710+
711+ def test_remove_server_from_containers_multiple (self ):
712+ """Test removing server that exists in multiple containers."""
713+ from code_assistant_manager .mcp .base_client import (
714+ _remove_server_from_containers ,
715+ )
716+
717+ config = {
718+ "mcpServers" : {"server1" : {}},
719+ "servers" : {"server1" : {}},
720+ }
721+
722+ result = _remove_server_from_containers (config , "server1" )
723+
724+ assert result is True
725+ assert "server1" not in config ["mcpServers" ]
726+ assert "server1" not in config ["servers" ]
727+
728+ def test_remove_server_from_containers_not_found (self ):
729+ """Test removing server that doesn't exist."""
730+ from code_assistant_manager .mcp .base_client import (
731+ _remove_server_from_containers ,
732+ )
733+
734+ config = {"mcpServers" : {"other" : {}}}
735+
736+ result = _remove_server_from_containers (config , "nonexistent" )
737+
738+ assert result is False
739+
740+ def test_find_server_container_mcpServers (self ):
741+ """Test finding server in mcpServers container."""
742+ from code_assistant_manager .mcp .base_client import _find_server_container
743+
744+ config = {"mcpServers" : {"test_server" : {"type" : "stdio" }}}
745+
746+ result = _find_server_container (config , "test_server" )
747+
748+ assert result is not None
749+ key , container = result
750+ assert key == "mcpServers"
751+ assert "test_server" in container
752+
753+ def test_find_server_container_not_found (self ):
754+ """Test finding server that doesn't exist."""
755+ from code_assistant_manager .mcp .base_client import _find_server_container
756+
757+ config = {"mcpServers" : {"other" : {}}}
758+
759+ result = _find_server_container (config , "nonexistent" )
760+
761+ assert result is None
0 commit comments