Skip to main content
Glama
test_config_loader.py9.69 kB
# # Copyright (C) 2024 Billy Bryant # Portions copyright (C) 2024 Sergey Parfenyuk (original MIT-licensed author) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. # # MIT License attribution: Portions of this file were originally licensed # under the MIT License by Sergey Parfenyuk (2024). # """Tests for the configuration loader module.""" import json import shutil import tempfile from collections.abc import Callable, Generator from pathlib import Path from typing import Any import pytest from mcp.client.stdio import StdioServerParameters from mcp_foxxy_bridge.config.config_loader import load_named_server_configs_from_file @pytest.fixture def create_temp_config_file() -> Generator[Callable[[dict[str, Any]], str], None, None]: """Creates a temporary JSON config file and returns its path.""" temp_files: list[str] = [] def _create_temp_config_file(config_content: dict[str, Any]) -> str: with tempfile.NamedTemporaryFile( mode="w", delete=False, suffix=".json", ) as tmp_config: json.dump(config_content, tmp_config) temp_files.append(tmp_config.name) return tmp_config.name yield _create_temp_config_file for f_path in temp_files: path = Path(f_path) if path.exists(): path.unlink() def test_load_valid_config(create_temp_config_file: Callable[[dict[str, Any]], str]) -> None: """Test loading a valid configuration file.""" config_content = { "mcpServers": { "server1": { "command": "echo", "args": ["hello"], "env": {"FOO": "bar"}, "enabled": True, }, "server2": { "command": "cat", "args": ["file.txt"], }, }, } tmp_config_path = create_temp_config_file(config_content) base_env = {"PASSED": "env_value"} base_env_with_added_env = {"PASSED": "env_value", "FOO": "bar"} loaded_params = load_named_server_configs_from_file(tmp_config_path, base_env) assert "server1" in loaded_params assert loaded_params["server1"].command == "echo" assert loaded_params["server1"].args == ["hello"] assert loaded_params["server1"].env == base_env_with_added_env # Env is a copy, check if it contains base_env items assert "server2" in loaded_params assert loaded_params["server2"].command == "cat" assert loaded_params["server2"].args == ["file.txt"] assert loaded_params["server2"].env == base_env def test_load_config_with_not_enabled_server( create_temp_config_file: Callable[[dict[str, Any]], str], ) -> None: """Test loading a configuration with disabled servers.""" config_content = { "mcpServers": { "explicitly_enabled_server": {"command": "true_command", "enabled": True}, # No 'enabled' flag, defaults to True "implicitly_enabled_server": {"command": "another_true_command"}, "not_enabled_server": {"command": "false_command", "enabled": False}, }, } tmp_config_path = create_temp_config_file(config_content) loaded_params = load_named_server_configs_from_file(tmp_config_path, {}) assert "explicitly_enabled_server" in loaded_params assert loaded_params["explicitly_enabled_server"].command == "true_command" assert "implicitly_enabled_server" in loaded_params assert loaded_params["implicitly_enabled_server"].command == "another_true_command" assert "not_enabled_server" not in loaded_params def test_file_not_found() -> None: """Test handling of non-existent configuration files.""" with pytest.raises(FileNotFoundError): load_named_server_configs_from_file("non_existent_file.json", {}) def test_json_decode_error() -> None: """Test handling of invalid JSON in configuration files.""" # Create a file with invalid JSON content with tempfile.NamedTemporaryFile( mode="w", delete=False, suffix=".json", ) as tmp_config: tmp_config.write("this is not json {") tmp_config_path = tmp_config.name # Use try/finally to ensure cleanup try: with pytest.raises(json.JSONDecodeError): load_named_server_configs_from_file(tmp_config_path, {}) finally: path = Path(tmp_config_path) if path.exists(): path.unlink() def test_load_example_fetch_config_if_uvx_exists() -> None: """Test loading the example fetch configuration if uvx is available.""" if not shutil.which("uvx"): pytest.skip("uvx command not found in PATH, skipping test for example config.") # Assuming the test is run from the root of the repository example_config_path = Path(__file__).parent.parent / "docs" / "examples" / "basic-config.json" if not example_config_path.exists(): pytest.fail( f"Example config file not found at expected path: {example_config_path}", ) base_env = {"EXAMPLE_ENV": "true"} loaded_params = load_named_server_configs_from_file(str(example_config_path), base_env) assert "fetch" in loaded_params fetch_param = loaded_params["fetch"] assert isinstance(fetch_param, StdioServerParameters) assert fetch_param.command == "uvx" assert fetch_param.args == ["mcp-server-fetch"] assert fetch_param.env == base_env # The 'timeout' and 'transportType' fields from the config are currently ignored by the loader, # so no need to assert them on StdioServerParameters. def test_invalid_config_format_missing_mcpservers( create_temp_config_file: Callable[[dict[str, Any]], str], ) -> None: """Test handling of configuration files missing the mcpServers key.""" config_content = {"some_other_key": "value"} tmp_config_path = create_temp_config_file(config_content) with pytest.raises(ValueError, match="Missing 'mcpServers' key"): load_named_server_configs_from_file(tmp_config_path, {}) def test_invalid_server_entry_not_dict( create_temp_config_file: Callable[[dict[str, Any]], str], ) -> None: """Test handling of server entries that are not dictionaries.""" config_content = {"mcpServers": {"server1": "not_a_dict"}} tmp_config_path = create_temp_config_file(config_content) # Schema validation should now catch this and raise an exception with pytest.raises(ValueError, match="Invalid configuration"): load_named_server_configs_from_file(tmp_config_path, {}) def test_server_entry_missing_command( create_temp_config_file: Callable[[dict[str, Any]], str], ) -> None: """Test handling of server entries missing the command field.""" config_content = {"mcpServers": {"server_no_command": {"args": ["arg1"]}}} tmp_config_path = create_temp_config_file(config_content) # Should raise ValueError due to schema validation failure with pytest.raises(ValueError, match="Invalid configuration"): load_named_server_configs_from_file(tmp_config_path, {}) def test_server_entry_invalid_args_type( create_temp_config_file: Callable[[dict[str, Any]], str], ) -> None: """Test handling of server entries with invalid args type.""" config_content = { "mcpServers": { "server_invalid_args": {"command": "mycmd", "args": "not_a_list"}, }, } tmp_config_path = create_temp_config_file(config_content) # Should raise ValueError due to schema validation failure with pytest.raises(ValueError, match="Invalid configuration"): load_named_server_configs_from_file(tmp_config_path, {}) def test_empty_mcpservers_dict(create_temp_config_file: Callable[[dict[str, Any]], str]) -> None: """Test handling of configuration files with empty mcpServers dictionary.""" config_content: dict[str, Any] = {"mcpServers": {}} tmp_config_path = create_temp_config_file(config_content) loaded_params = load_named_server_configs_from_file(tmp_config_path, {}) assert len(loaded_params) == 0 def test_config_file_is_empty_json_object(create_temp_config_file: Callable[[dict[str, Any]], str]) -> None: """Test handling of configuration files with empty JSON objects.""" config_content: dict[str, Any] = {} # Empty JSON object tmp_config_path = create_temp_config_file(config_content) with pytest.raises(ValueError, match="Missing 'mcpServers' key"): load_named_server_configs_from_file(tmp_config_path, {}) def test_config_file_is_empty_string() -> None: """Test handling of configuration files with empty content.""" # Create a file with an empty string with tempfile.NamedTemporaryFile( mode="w", delete=False, suffix=".json", ) as tmp_config: tmp_config.write("") # Empty content tmp_config_path = tmp_config.name try: with pytest.raises(json.JSONDecodeError): load_named_server_configs_from_file(tmp_config_path, {}) finally: path = Path(tmp_config_path) if path.exists(): path.unlink()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/billyjbryant/mcp-foxxy-bridge'

If you have feedback or need assistance with the MCP directory API, please join our Discord server