mcp-proxy-adapter 3.0.0__py3-none-any.whl → 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. examples/basic_example/README.md +123 -9
  2. examples/basic_example/config.json +4 -0
  3. examples/basic_example/docs/EN/README.md +46 -5
  4. examples/basic_example/docs/RU/README.md +46 -5
  5. examples/basic_example/server.py +127 -21
  6. examples/complete_example/commands/system_command.py +1 -0
  7. examples/complete_example/server.py +65 -11
  8. examples/minimal_example/README.md +20 -6
  9. examples/minimal_example/config.json +7 -14
  10. examples/minimal_example/main.py +109 -40
  11. examples/minimal_example/simple_server.py +53 -14
  12. examples/minimal_example/tests/conftest.py +1 -1
  13. examples/minimal_example/tests/test_integration.py +8 -10
  14. examples/simple_server.py +12 -21
  15. examples/test_server.py +22 -14
  16. examples/tool_description_example.py +82 -0
  17. mcp_proxy_adapter/api/__init__.py +0 -0
  18. mcp_proxy_adapter/api/app.py +391 -0
  19. mcp_proxy_adapter/api/handlers.py +229 -0
  20. mcp_proxy_adapter/api/middleware/__init__.py +49 -0
  21. mcp_proxy_adapter/api/middleware/auth.py +146 -0
  22. mcp_proxy_adapter/api/middleware/base.py +79 -0
  23. mcp_proxy_adapter/api/middleware/error_handling.py +198 -0
  24. mcp_proxy_adapter/api/middleware/logging.py +96 -0
  25. mcp_proxy_adapter/api/middleware/performance.py +83 -0
  26. mcp_proxy_adapter/api/middleware/rate_limit.py +152 -0
  27. mcp_proxy_adapter/api/schemas.py +305 -0
  28. mcp_proxy_adapter/api/tool_integration.py +223 -0
  29. mcp_proxy_adapter/api/tools.py +198 -0
  30. mcp_proxy_adapter/commands/__init__.py +19 -0
  31. mcp_proxy_adapter/commands/base.py +301 -0
  32. mcp_proxy_adapter/commands/command_registry.py +231 -0
  33. mcp_proxy_adapter/commands/config_command.py +113 -0
  34. mcp_proxy_adapter/commands/health_command.py +136 -0
  35. mcp_proxy_adapter/commands/help_command.py +193 -0
  36. mcp_proxy_adapter/commands/result.py +215 -0
  37. mcp_proxy_adapter/config.py +9 -0
  38. mcp_proxy_adapter/core/__init__.py +0 -0
  39. mcp_proxy_adapter/core/errors.py +173 -0
  40. mcp_proxy_adapter/core/logging.py +205 -0
  41. mcp_proxy_adapter/core/utils.py +138 -0
  42. mcp_proxy_adapter/py.typed +0 -0
  43. mcp_proxy_adapter/schemas/base_schema.json +114 -0
  44. mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
  45. mcp_proxy_adapter/tests/__init__.py +0 -0
  46. mcp_proxy_adapter/tests/api/__init__.py +3 -0
  47. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +115 -0
  48. mcp_proxy_adapter/tests/api/test_middleware.py +336 -0
  49. mcp_proxy_adapter/tests/commands/__init__.py +3 -0
  50. mcp_proxy_adapter/tests/commands/test_config_command.py +211 -0
  51. mcp_proxy_adapter/tests/commands/test_echo_command.py +127 -0
  52. mcp_proxy_adapter/tests/commands/test_help_command.py +133 -0
  53. mcp_proxy_adapter/tests/conftest.py +131 -0
  54. mcp_proxy_adapter/tests/functional/__init__.py +3 -0
  55. mcp_proxy_adapter/tests/functional/test_api.py +235 -0
  56. mcp_proxy_adapter/tests/integration/__init__.py +3 -0
  57. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +130 -0
  58. mcp_proxy_adapter/tests/integration/test_integration.py +255 -0
  59. mcp_proxy_adapter/tests/performance/__init__.py +3 -0
  60. mcp_proxy_adapter/tests/performance/test_performance.py +189 -0
  61. mcp_proxy_adapter/tests/stubs/__init__.py +10 -0
  62. mcp_proxy_adapter/tests/stubs/echo_command.py +104 -0
  63. mcp_proxy_adapter/tests/test_api_endpoints.py +271 -0
  64. mcp_proxy_adapter/tests/test_api_handlers.py +289 -0
  65. mcp_proxy_adapter/tests/test_base_command.py +123 -0
  66. mcp_proxy_adapter/tests/test_batch_requests.py +117 -0
  67. mcp_proxy_adapter/tests/test_command_registry.py +245 -0
  68. mcp_proxy_adapter/tests/test_config.py +127 -0
  69. mcp_proxy_adapter/tests/test_utils.py +65 -0
  70. mcp_proxy_adapter/tests/unit/__init__.py +3 -0
  71. mcp_proxy_adapter/tests/unit/test_base_command.py +130 -0
  72. mcp_proxy_adapter/tests/unit/test_config.py +217 -0
  73. mcp_proxy_adapter/version.py +1 -1
  74. {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/METADATA +1 -1
  75. mcp_proxy_adapter-3.0.1.dist-info/RECORD +109 -0
  76. examples/basic_example/config.yaml +0 -20
  77. examples/basic_example/main.py +0 -50
  78. examples/complete_example/main.py +0 -67
  79. examples/minimal_example/config.yaml +0 -26
  80. mcp_proxy_adapter/framework.py +0 -109
  81. mcp_proxy_adapter-3.0.0.dist-info/RECORD +0 -58
  82. {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/WHEEL +0 -0
  83. {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/licenses/LICENSE +0 -0
  84. {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,117 @@
1
+ """
2
+ Tests for JSON-RPC batch requests handling.
3
+ """
4
+
5
+ import pytest
6
+ from typing import Dict, Any, List
7
+ from unittest.mock import AsyncMock, patch, MagicMock
8
+
9
+ from mcp_proxy_adapter.api.handlers import handle_json_rpc, handle_batch_json_rpc
10
+ from mcp_proxy_adapter.commands.result import SuccessResult
11
+ from mcp_proxy_adapter.core.errors import NotFoundError, MicroserviceError
12
+
13
+
14
+ @pytest.fixture
15
+ def success_result():
16
+ """Fixture for test success result."""
17
+ result = SuccessResult(data={"key": "value"}, message="Success")
18
+ return result
19
+
20
+
21
+ class TestBatchJsonRpc:
22
+ """Tests for JSON-RPC batch requests handling."""
23
+
24
+ @pytest.mark.asyncio
25
+ @patch("mcp_proxy_adapter.api.handlers.handle_json_rpc")
26
+ async def test_batch_request_processing(self, mock_handle_json_rpc):
27
+ """Test handling of batch requests."""
28
+ # Setup handle_json_rpc mock to return different responses
29
+ mock_handle_json_rpc.side_effect = [
30
+ # First request - success
31
+ {
32
+ "jsonrpc": "2.0",
33
+ "result": {"key1": "value1"},
34
+ "id": "1"
35
+ },
36
+ # Second request - error
37
+ {
38
+ "jsonrpc": "2.0",
39
+ "error": {
40
+ "code": -32601,
41
+ "message": "Method not found"
42
+ },
43
+ "id": "2"
44
+ },
45
+ # Third request - success
46
+ {
47
+ "jsonrpc": "2.0",
48
+ "result": {"key3": "value3"},
49
+ "id": "3"
50
+ }
51
+ ]
52
+
53
+ # Create batch request
54
+ batch_request = [
55
+ {"jsonrpc": "2.0", "method": "method1", "params": {"p1": "v1"}, "id": "1"},
56
+ {"jsonrpc": "2.0", "method": "non_existent", "id": "2"},
57
+ {"jsonrpc": "2.0", "method": "method3", "params": {"p3": "v3"}, "id": "3"}
58
+ ]
59
+
60
+ # Process batch request
61
+ responses = await handle_batch_json_rpc(batch_request)
62
+
63
+ # Assertions
64
+ assert len(responses) == 3
65
+ assert mock_handle_json_rpc.call_count == 3
66
+
67
+ # Check first response
68
+ assert responses[0]["jsonrpc"] == "2.0"
69
+ assert responses[0]["result"] == {"key1": "value1"}
70
+ assert responses[0]["id"] == "1"
71
+
72
+ # Check second response (error)
73
+ assert responses[1]["jsonrpc"] == "2.0"
74
+ assert responses[1]["error"]["code"] == -32601
75
+ assert responses[1]["id"] == "2"
76
+
77
+ # Check third response
78
+ assert responses[2]["jsonrpc"] == "2.0"
79
+ assert responses[2]["result"] == {"key3": "value3"}
80
+ assert responses[2]["id"] == "3"
81
+
82
+ @pytest.mark.asyncio
83
+ @patch("mcp_proxy_adapter.api.handlers.handle_json_rpc")
84
+ async def test_empty_batch_request(self, mock_handle_json_rpc):
85
+ """Test handling of empty batch request."""
86
+ # Create empty batch request
87
+ batch_request = []
88
+
89
+ # Process batch request
90
+ responses = await handle_batch_json_rpc(batch_request)
91
+
92
+ # Assertions
93
+ assert len(responses) == 0
94
+ assert mock_handle_json_rpc.call_count == 0
95
+
96
+ @pytest.mark.asyncio
97
+ @patch("mcp_proxy_adapter.api.handlers.execute_command")
98
+ async def test_end_to_end_batch_processing(self, mock_execute_command, success_result):
99
+ """Test end-to-end processing of batch requests."""
100
+ # Setup execute_command mock
101
+ mock_execute_command.return_value = success_result.to_dict()
102
+
103
+ # Create batch request
104
+ batch_request = [
105
+ {"jsonrpc": "2.0", "method": "method1", "params": {"p1": "v1"}, "id": "1"},
106
+ {"jsonrpc": "2.0", "method": "method2", "params": {"p2": "v2"}, "id": "2"}
107
+ ]
108
+
109
+ # Process batch request
110
+ responses = await handle_batch_json_rpc(batch_request)
111
+
112
+ # Assertions
113
+ assert len(responses) == 2
114
+ for response in responses:
115
+ assert response["jsonrpc"] == "2.0"
116
+ assert "result" in response
117
+ assert "error" not in response
@@ -0,0 +1,245 @@
1
+ """
2
+ Tests for command registry.
3
+ """
4
+
5
+ import pytest
6
+ from unittest.mock import MagicMock, patch
7
+
8
+ from mcp_proxy_adapter.commands.base import Command
9
+ from mcp_proxy_adapter.commands.result import CommandResult
10
+ from mcp_proxy_adapter.commands.command_registry import CommandRegistry
11
+ from mcp_proxy_adapter.core.errors import NotFoundError
12
+
13
+
14
+ class MockResult(CommandResult):
15
+ """Test result class for testing."""
16
+
17
+ def __init__(self):
18
+ pass
19
+
20
+ def to_dict(self):
21
+ return {}
22
+
23
+ @classmethod
24
+ def get_schema(cls):
25
+ return {}
26
+
27
+
28
+ class TestCommand1(Command):
29
+ """First test command."""
30
+
31
+ name = "test_command1"
32
+ result_class = MockResult
33
+
34
+ async def execute(self, **kwargs):
35
+ return MockResult()
36
+
37
+
38
+ class TestCommand2(Command):
39
+ """Second test command."""
40
+
41
+ name = "test_command2"
42
+ result_class = MockResult
43
+
44
+ async def execute(self, **kwargs):
45
+ return MockResult()
46
+
47
+
48
+ def test_registry_initialization():
49
+ """Test registry initialization."""
50
+ registry = CommandRegistry()
51
+ assert len(registry._commands) == 0
52
+
53
+
54
+ def test_register_command():
55
+ """Test registering command."""
56
+ registry = CommandRegistry()
57
+
58
+ # Register first command
59
+ registry.register(TestCommand1)
60
+ assert len(registry._commands) == 1
61
+ assert "test_command1" in registry._commands
62
+
63
+ # Register second command
64
+ registry.register(TestCommand2)
65
+ assert len(registry._commands) == 2
66
+ assert "test_command2" in registry._commands
67
+
68
+
69
+ def test_register_duplicated_command():
70
+ """Test registering duplicated command."""
71
+ registry = CommandRegistry()
72
+
73
+ # Register command
74
+ registry.register(TestCommand1)
75
+
76
+ # Try to register again
77
+ with pytest.raises(ValueError):
78
+ registry.register(TestCommand1)
79
+
80
+
81
+ def test_register_command_without_name():
82
+ """Test registering command without name attribute."""
83
+ registry = CommandRegistry()
84
+
85
+ # Create command without name
86
+ class CommandWithoutName(Command):
87
+ result_class = MockResult
88
+
89
+ async def execute(self, **kwargs):
90
+ return MockResult()
91
+
92
+ # Register command
93
+ registry.register(CommandWithoutName)
94
+
95
+ # Check if registered with class name
96
+ assert "commandwithoutname" in registry._commands
97
+
98
+
99
+ def test_unregister_command():
100
+ """Test unregistering command."""
101
+ registry = CommandRegistry()
102
+
103
+ # Register command
104
+ registry.register(TestCommand1)
105
+ assert "test_command1" in registry._commands
106
+
107
+ # Unregister command
108
+ registry.unregister("test_command1")
109
+ assert "test_command1" not in registry._commands
110
+
111
+
112
+ def test_unregister_nonexistent_command():
113
+ """Test unregistering nonexistent command."""
114
+ registry = CommandRegistry()
115
+
116
+ # Try to unregister nonexistent command
117
+ with pytest.raises(NotFoundError):
118
+ registry.unregister("nonexistent")
119
+
120
+
121
+ def test_get_command():
122
+ """Test getting command."""
123
+ registry = CommandRegistry()
124
+
125
+ # Register command
126
+ registry.register(TestCommand1)
127
+
128
+ # Get command
129
+ command = registry.get_command("test_command1")
130
+ assert command == TestCommand1
131
+
132
+
133
+ def test_get_nonexistent_command():
134
+ """Test getting nonexistent command."""
135
+ registry = CommandRegistry()
136
+
137
+ # Try to get nonexistent command
138
+ with pytest.raises(NotFoundError):
139
+ registry.get_command("nonexistent")
140
+
141
+
142
+ def test_get_all_commands():
143
+ """Test getting all commands."""
144
+ registry = CommandRegistry()
145
+
146
+ # Register commands
147
+ registry.register(TestCommand1)
148
+ registry.register(TestCommand2)
149
+
150
+ # Get all commands
151
+ commands = registry.get_all_commands()
152
+ assert len(commands) == 2
153
+ assert "test_command1" in commands
154
+ assert "test_command2" in commands
155
+ assert commands["test_command1"] == TestCommand1
156
+ assert commands["test_command2"] == TestCommand2
157
+
158
+
159
+ def test_get_command_info():
160
+ """Test getting command info."""
161
+ registry = CommandRegistry()
162
+
163
+ # Register command
164
+ registry.register(TestCommand1)
165
+
166
+ # Get command info
167
+ info = registry.get_command_info("test_command1")
168
+
169
+ # Check info structure
170
+ assert info["name"] == "test_command1"
171
+ assert "description" in info
172
+ assert "params" in info
173
+ assert "schema" in info
174
+ assert "result_schema" in info
175
+
176
+
177
+ def test_get_all_commands_info():
178
+ """Test getting all commands info."""
179
+ registry = CommandRegistry()
180
+
181
+ # Register commands
182
+ registry.register(TestCommand1)
183
+ registry.register(TestCommand2)
184
+
185
+ # Get all commands info
186
+ info = registry.get_all_commands_info()
187
+
188
+ # Check info structure
189
+ assert len(info) == 2
190
+ assert "test_command1" in info
191
+ assert "test_command2" in info
192
+ assert info["test_command1"]["name"] == "test_command1"
193
+ assert info["test_command2"]["name"] == "test_command2"
194
+
195
+
196
+ @patch("importlib.import_module")
197
+ @patch("pkgutil.iter_modules")
198
+ @patch("inspect.getmembers")
199
+ def test_discover_commands(mock_getmembers, mock_iter_modules, mock_import_module):
200
+ """Test discovering commands."""
201
+ registry = CommandRegistry()
202
+
203
+ # Mock package
204
+ mock_package = MagicMock()
205
+ mock_package.__file__ = "/path/to/package"
206
+ mock_import_module.return_value = mock_package
207
+
208
+ # Mock modules
209
+ mock_iter_modules.return_value = [
210
+ (None, "test_command", False),
211
+ (None, "other_module", False)
212
+ ]
213
+
214
+ # Mock command classes
215
+ class DiscoveredCommand(Command):
216
+ name = "discovered"
217
+ result_class = MockResult
218
+
219
+ async def execute(self, **kwargs):
220
+ return MockResult()
221
+
222
+ # Mock getmembers to return command class
223
+ mock_getmembers.return_value = [
224
+ ("DiscoveredCommand", DiscoveredCommand)
225
+ ]
226
+
227
+ # Discover commands
228
+ registry.discover_commands()
229
+
230
+ # Check if command was registered
231
+ assert len(mock_import_module.mock_calls) > 0
232
+
233
+
234
+ def test_clear_registry():
235
+ """Test clearing registry."""
236
+ registry = CommandRegistry()
237
+
238
+ # Register commands
239
+ registry.register(TestCommand1)
240
+ registry.register(TestCommand2)
241
+ assert len(registry._commands) == 2
242
+
243
+ # Clear registry
244
+ registry.clear()
245
+ assert len(registry._commands) == 0
@@ -0,0 +1,127 @@
1
+ """
2
+ Test module for configuration class.
3
+ """
4
+
5
+ import os
6
+ import json
7
+ import tempfile
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ from mcp_proxy_adapter.config import Config
13
+
14
+
15
+ def test_config_initialization(temp_config_file):
16
+ """
17
+ Test configuration initialization.
18
+ """
19
+ config = Config(temp_config_file)
20
+ assert config.config_path == temp_config_file
21
+ assert isinstance(config.config_data, dict)
22
+
23
+
24
+ def test_config_get_existing_values(test_config):
25
+ """
26
+ Test getting existing values from configuration.
27
+ """
28
+ # Test top-level key
29
+ server_config = test_config.get("server")
30
+ assert server_config.get("host") == "127.0.0.1"
31
+ assert server_config.get("port") == 8888
32
+
33
+ # Test nested key with dot notation
34
+ assert test_config.get("server.host") == "127.0.0.1"
35
+ assert test_config.get("server.port") == 8888
36
+
37
+ # Test logging values
38
+ assert test_config.get("logging.level") == "DEBUG"
39
+ assert test_config.get("logging.file") is None
40
+
41
+
42
+ def test_config_get_default_values(test_config):
43
+ """
44
+ Test getting default values for non-existent keys.
45
+ """
46
+ # Non-existent key with default
47
+ assert test_config.get("non_existent", "default") == "default"
48
+
49
+ # Non-existent nested key with default
50
+ assert test_config.get("server.non_existent", 42) == 42
51
+
52
+ # Non-existent top level with nested key
53
+ assert test_config.get("non_existent.key", False) is False
54
+
55
+
56
+ def test_config_set_values(test_config):
57
+ """
58
+ Test setting values in configuration.
59
+ """
60
+ # Set top-level value
61
+ test_config.set("new_key", "value")
62
+ assert test_config.get("new_key") == "value"
63
+
64
+ # Set nested value for existing parent
65
+ test_config.set("server.api_key", "secret")
66
+ assert test_config.get("server.api_key") == "secret"
67
+
68
+ # Set nested value with non-existent parent
69
+ test_config.set("database.url", "postgres://localhost")
70
+ assert test_config.get("database.url") == "postgres://localhost"
71
+
72
+ # Overwrite existing value
73
+ test_config.set("server.host", "0.0.0.0")
74
+ assert test_config.get("server.host") == "0.0.0.0"
75
+
76
+
77
+ def test_config_save_load(temp_config_file):
78
+ """
79
+ Test saving and loading configuration.
80
+ """
81
+ # Create and modify config
82
+ config = Config(temp_config_file)
83
+ config.set("test_key", "test_value")
84
+ config.set("nested.key", 123)
85
+
86
+ # Save config
87
+ config.save()
88
+
89
+ # Create new config instance to load from the same file
90
+ new_config = Config(temp_config_file)
91
+
92
+ # Verify values were saved and loaded
93
+ assert new_config.get("test_key") == "test_value"
94
+ assert new_config.get("nested.key") == 123
95
+ assert new_config.get("server.host") == "127.0.0.1"
96
+
97
+
98
+ def test_config_environment_variables():
99
+ """
100
+ Test environment variables override configuration values.
101
+ """
102
+ with tempfile.NamedTemporaryFile(suffix=".json", delete=False, mode="w") as temp:
103
+ temp_path = temp.name
104
+ json.dump({"server": {"host": "localhost", "port": 8000}}, temp)
105
+
106
+ try:
107
+ # Set environment variables
108
+ os.environ["SERVICE_SERVER_HOST"] = "192.168.1.1"
109
+ os.environ["SERVICE_SERVER_PORT"] = "9000"
110
+ os.environ["SERVICE_LOGGING_LEVEL"] = "INFO"
111
+
112
+ # Create config that should load from env vars
113
+ config = Config(temp_path)
114
+
115
+ # Check values are overridden by environment
116
+ assert config.get("server.host") == "192.168.1.1"
117
+ assert config.get("server.port") == 9000
118
+ assert config.get("logging.level") == "INFO"
119
+ finally:
120
+ # Clean up
121
+ os.unlink(temp_path)
122
+ if "SERVICE_SERVER_HOST" in os.environ:
123
+ del os.environ["SERVICE_SERVER_HOST"]
124
+ if "SERVICE_SERVER_PORT" in os.environ:
125
+ del os.environ["SERVICE_SERVER_PORT"]
126
+ if "SERVICE_LOGGING_LEVEL" in os.environ:
127
+ del os.environ["SERVICE_LOGGING_LEVEL"]
@@ -0,0 +1,65 @@
1
+ """
2
+ Test utilities for mcp_microservice.
3
+ """
4
+
5
+ from typing import Any, Dict
6
+ from mcp_proxy_adapter.commands.result import CommandResult
7
+
8
+
9
+ class MockResult(CommandResult):
10
+ """
11
+ Mock result class for testing.
12
+
13
+ Attributes:
14
+ message: Test message.
15
+ timestamp: Test timestamp.
16
+ """
17
+
18
+ def __init__(self, message: str = "Test message", timestamp: float = 12345.0):
19
+ self.message = message
20
+ self.timestamp = timestamp
21
+
22
+ def to_dict(self) -> Dict[str, Any]:
23
+ """
24
+ Converts result to dictionary for serialization.
25
+
26
+ Returns:
27
+ Dictionary with result data.
28
+ """
29
+ return {
30
+ "message": self.message,
31
+ "timestamp": self.timestamp
32
+ }
33
+
34
+ @classmethod
35
+ def get_schema(cls) -> Dict[str, Any]:
36
+ """
37
+ Returns JSON schema for result validation.
38
+
39
+ Returns:
40
+ Dictionary with JSON schema.
41
+ """
42
+ return {
43
+ "type": "object",
44
+ "properties": {
45
+ "message": {"type": "string", "description": "Test message"},
46
+ "timestamp": {"type": "number", "description": "Test timestamp"}
47
+ },
48
+ "required": ["message", "timestamp"]
49
+ }
50
+
51
+ @classmethod
52
+ def from_dict(cls, data: Dict[str, Any]) -> "MockResult":
53
+ """
54
+ Creates result instance from dictionary.
55
+
56
+ Args:
57
+ data: Dictionary with result data.
58
+
59
+ Returns:
60
+ MockResult instance.
61
+ """
62
+ return cls(
63
+ message=data.get("message", ""),
64
+ timestamp=data.get("timestamp", 0.0)
65
+ )
@@ -0,0 +1,3 @@
1
+ """
2
+ Unit tests for the mcp_microservice package.
3
+ """
@@ -0,0 +1,130 @@
1
+ """
2
+ Tests for command base classes.
3
+ """
4
+
5
+ import pytest
6
+ from typing import Dict, Any
7
+
8
+ from mcp_proxy_adapter.commands.base import Command
9
+ from mcp_proxy_adapter.commands.result import CommandResult, SuccessResult, ErrorResult
10
+
11
+
12
+ class MockResultClass(CommandResult):
13
+ """Test result class for testing."""
14
+
15
+ def __init__(self, value: str):
16
+ self.value = value
17
+
18
+ def to_dict(self) -> Dict[str, Any]:
19
+ return {"value": self.value}
20
+
21
+ @classmethod
22
+ def get_schema(cls) -> Dict[str, Any]:
23
+ return {
24
+ "type": "object",
25
+ "properties": {
26
+ "value": {"type": "string"}
27
+ },
28
+ "required": ["value"]
29
+ }
30
+
31
+
32
+ class TestCommand(Command):
33
+ """Test command for testing."""
34
+
35
+ name = "test_command"
36
+ result_class = MockResultClass
37
+
38
+ async def execute(self, value: str = "default") -> MockResultClass:
39
+ return MockResultClass(value)
40
+
41
+ @classmethod
42
+ def get_schema(cls) -> Dict[str, Any]:
43
+ return {
44
+ "type": "object",
45
+ "properties": {
46
+ "value": {"type": "string"}
47
+ },
48
+ "additionalProperties": False
49
+ }
50
+
51
+
52
+ @pytest.mark.unit
53
+ def test_success_result():
54
+ """Test success result class."""
55
+ result = SuccessResult(data={"key": "value"}, message="success message")
56
+
57
+ # Test to_dict method
58
+ result_dict = result.to_dict()
59
+ assert result_dict["success"] is True
60
+ assert result_dict["data"] == {"key": "value"}
61
+ assert result_dict["message"] == "success message"
62
+
63
+ # Test from_dict method
64
+ result2 = SuccessResult.from_dict(result_dict)
65
+ assert result2.data == {"key": "value"}
66
+ assert result2.message == "success message"
67
+
68
+
69
+ @pytest.mark.unit
70
+ def test_error_result():
71
+ """Test error result class."""
72
+ result = ErrorResult(message="error message", code=400, details={"field": "invalid"})
73
+
74
+ # Test to_dict method
75
+ result_dict = result.to_dict()
76
+ assert result_dict["success"] is False
77
+ assert result_dict["error"]["code"] == 400
78
+ assert result_dict["error"]["message"] == "error message"
79
+ assert result_dict["error"]["data"] == {"field": "invalid"}
80
+
81
+ # Test from_dict method
82
+ result2 = ErrorResult.from_dict(result_dict)
83
+ assert result2.message == "error message"
84
+ assert result2.code == 400
85
+ assert result2.details == {"field": "invalid"}
86
+
87
+
88
+ class TestCommandClass:
89
+ """Test command class."""
90
+
91
+ @pytest.mark.unit
92
+ @pytest.mark.asyncio
93
+ async def test_execute(self):
94
+ """Test execute method."""
95
+ command = TestCommand()
96
+ result = await command.execute(value="test_value")
97
+ assert isinstance(result, MockResultClass)
98
+ assert result.value == "test_value"
99
+
100
+ @pytest.mark.unit
101
+ @pytest.mark.asyncio
102
+ async def test_run(self):
103
+ """Test run method (with validation)."""
104
+ result = await TestCommand.run(value="test_value")
105
+ assert isinstance(result, MockResultClass)
106
+ assert result.value == "test_value"
107
+
108
+ @pytest.mark.unit
109
+ def test_get_schema(self):
110
+ """Test get_schema method."""
111
+ schema = TestCommand.get_schema()
112
+ assert schema["type"] == "object"
113
+ assert "value" in schema["properties"]
114
+ assert schema["additionalProperties"] is False
115
+
116
+ @pytest.mark.unit
117
+ def test_get_result_schema(self):
118
+ """Test get_result_schema method."""
119
+ schema = TestCommand.get_result_schema()
120
+ assert schema["type"] == "object"
121
+ assert "value" in schema["properties"]
122
+ assert "value" in schema["required"]
123
+
124
+ @pytest.mark.unit
125
+ def test_get_param_info(self):
126
+ """Test get_param_info method."""
127
+ params = TestCommand.get_param_info()
128
+ assert "value" in params
129
+ assert params["value"]["required"] is False
130
+ assert params["value"]["default"] == "default"