mcp-proxy-adapter 3.0.0__py3-none-any.whl → 3.0.2__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 (85) 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 +68 -40
  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/custom_openapi.py +47 -10
  43. mcp_proxy_adapter/py.typed +0 -0
  44. mcp_proxy_adapter/schemas/base_schema.json +114 -0
  45. mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
  46. mcp_proxy_adapter/tests/__init__.py +0 -0
  47. mcp_proxy_adapter/tests/api/__init__.py +3 -0
  48. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +115 -0
  49. mcp_proxy_adapter/tests/api/test_middleware.py +336 -0
  50. mcp_proxy_adapter/tests/commands/__init__.py +3 -0
  51. mcp_proxy_adapter/tests/commands/test_config_command.py +211 -0
  52. mcp_proxy_adapter/tests/commands/test_echo_command.py +127 -0
  53. mcp_proxy_adapter/tests/commands/test_help_command.py +133 -0
  54. mcp_proxy_adapter/tests/conftest.py +131 -0
  55. mcp_proxy_adapter/tests/functional/__init__.py +3 -0
  56. mcp_proxy_adapter/tests/functional/test_api.py +253 -0
  57. mcp_proxy_adapter/tests/integration/__init__.py +3 -0
  58. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +130 -0
  59. mcp_proxy_adapter/tests/integration/test_integration.py +255 -0
  60. mcp_proxy_adapter/tests/performance/__init__.py +3 -0
  61. mcp_proxy_adapter/tests/performance/test_performance.py +189 -0
  62. mcp_proxy_adapter/tests/stubs/__init__.py +10 -0
  63. mcp_proxy_adapter/tests/stubs/echo_command.py +104 -0
  64. mcp_proxy_adapter/tests/test_api_endpoints.py +271 -0
  65. mcp_proxy_adapter/tests/test_api_handlers.py +289 -0
  66. mcp_proxy_adapter/tests/test_base_command.py +123 -0
  67. mcp_proxy_adapter/tests/test_batch_requests.py +117 -0
  68. mcp_proxy_adapter/tests/test_command_registry.py +245 -0
  69. mcp_proxy_adapter/tests/test_config.py +127 -0
  70. mcp_proxy_adapter/tests/test_utils.py +65 -0
  71. mcp_proxy_adapter/tests/unit/__init__.py +3 -0
  72. mcp_proxy_adapter/tests/unit/test_base_command.py +130 -0
  73. mcp_proxy_adapter/tests/unit/test_config.py +217 -0
  74. mcp_proxy_adapter/version.py +1 -1
  75. {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.2.dist-info}/METADATA +1 -1
  76. mcp_proxy_adapter-3.0.2.dist-info/RECORD +109 -0
  77. examples/basic_example/config.yaml +0 -20
  78. examples/basic_example/main.py +0 -50
  79. examples/complete_example/main.py +0 -67
  80. examples/minimal_example/config.yaml +0 -26
  81. mcp_proxy_adapter/framework.py +0 -109
  82. mcp_proxy_adapter-3.0.0.dist-info/RECORD +0 -58
  83. {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.2.dist-info}/WHEEL +0 -0
  84. {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.2.dist-info}/licenses/LICENSE +0 -0
  85. {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,271 @@
1
+ """
2
+ Tests for API endpoints.
3
+ """
4
+
5
+ import pytest
6
+ from typing import Dict, Any
7
+ from unittest.mock import patch, MagicMock, AsyncMock
8
+ from fastapi.testclient import TestClient
9
+ import asyncio
10
+
11
+ from mcp_proxy_adapter.api.app import create_app
12
+ from mcp_proxy_adapter.commands.result import SuccessResult
13
+ from mcp_proxy_adapter.core.errors import NotFoundError
14
+
15
+
16
+ @pytest.fixture
17
+ def client():
18
+ """Fixture for test client."""
19
+ # Создаем приложение с очищенным реестром команд для тестов
20
+ with patch("mcp_proxy_adapter.commands.registry.discover_commands"): # Предотвращаем автообнаружение команд
21
+ app = create_app()
22
+ return TestClient(app)
23
+
24
+
25
+ @pytest.fixture
26
+ def success_result():
27
+ """Fixture for test success result."""
28
+ result = SuccessResult(data={"key": "value"}, message="Success")
29
+ return result
30
+
31
+
32
+ class TestCommandsEndpoint:
33
+ """Tests for the /api/commands endpoint."""
34
+
35
+ @patch("mcp_proxy_adapter.api.app.get_commands_list")
36
+ def test_commands_list_endpoint(self, mock_get_commands_list, client):
37
+ """Test getting list of available commands."""
38
+ # Create mock commands info
39
+ mock_commands_info = {
40
+ "command1": {
41
+ "name": "command1",
42
+ "description": "Test command 1",
43
+ "params": {},
44
+ "schema": {"type": "object"},
45
+ "result_schema": {"type": "object"}
46
+ },
47
+ "command2": {
48
+ "name": "command2",
49
+ "description": "Test command 2",
50
+ "params": {},
51
+ "schema": {"type": "object"},
52
+ "result_schema": {"type": "object"}
53
+ }
54
+ }
55
+
56
+ # Setup mock для асинхронного метода
57
+ mock_get_commands_list.return_value = mock_commands_info
58
+
59
+ # Get commands list
60
+ response = client.get("/api/commands")
61
+
62
+ # Assertions
63
+ assert response.status_code == 200
64
+ assert response.json() == {"commands": mock_commands_info}
65
+ mock_get_commands_list.assert_called_once()
66
+
67
+
68
+ class TestHealthEndpoint:
69
+ """Tests for the /health endpoint."""
70
+
71
+ @patch("mcp_proxy_adapter.api.app.get_server_health")
72
+ def test_health_endpoint(self, mock_get_server_health, client):
73
+ """Test getting server health information."""
74
+ # Create mock health info
75
+ mock_health_info = {
76
+ "status": "ok",
77
+ "model": "mcp-proxy-adapter",
78
+ "version": "1.0.0"
79
+ }
80
+
81
+ # Setup mock для асинхронного метода
82
+ mock_get_server_health.return_value = mock_health_info
83
+
84
+ # Get health info
85
+ response = client.get("/health")
86
+
87
+ # Assertions
88
+ assert response.status_code == 200
89
+ response_data = response.json()
90
+ assert "status" in response_data
91
+ assert response_data["status"] == "ok"
92
+ assert "version" in response_data
93
+
94
+
95
+ class TestJsonRpcEndpoint:
96
+ """Tests for JSON-RPC endpoint."""
97
+
98
+ @pytest.mark.asyncio
99
+ async def test_jsonrpc_endpoint_empty_batch(self, client):
100
+ """Test JSON-RPC endpoint handles empty batch requests."""
101
+ # Make request with empty array
102
+ response = client.post("/api/jsonrpc", json=[])
103
+
104
+ # Assertions
105
+ assert response.status_code == 400
106
+ data = response.json()
107
+ assert data["jsonrpc"] == "2.0"
108
+ assert "error" in data
109
+ assert data["error"]["code"] == -32600
110
+ assert "Empty batch request" in data["error"]["message"]
111
+
112
+ @pytest.mark.asyncio
113
+ @patch("mcp_proxy_adapter.commands.command_registry.registry.get_command")
114
+ async def test_handle_json_rpc_success(self, mock_get_command):
115
+ """Test handle_json_rpc function with success result."""
116
+ from mcp_proxy_adapter.commands.result import SuccessResult
117
+
118
+ # Create mock command class
119
+ mock_command = MagicMock()
120
+ mock_command.run = AsyncMock(return_value=SuccessResult(
121
+ data={"status": "success"},
122
+ message="Success message"
123
+ ))
124
+ mock_get_command.return_value = mock_command
125
+
126
+ # Create JSON-RPC request
127
+ request_data = {
128
+ "jsonrpc": "2.0",
129
+ "method": "test_command",
130
+ "params": {"param": "value"},
131
+ "id": "1"
132
+ }
133
+
134
+ # Call handler directly
135
+ from mcp_proxy_adapter.api.handlers import handle_json_rpc
136
+ response = await handle_json_rpc(request_data)
137
+
138
+ # Assertions
139
+ assert response["jsonrpc"] == "2.0"
140
+ assert "result" in response
141
+ assert response["result"]["data"]["status"] == "success"
142
+ assert response["id"] == "1"
143
+ mock_get_command.assert_called_once_with("test_command")
144
+ mock_command.run.assert_called_once_with(param="value")
145
+
146
+ @pytest.mark.asyncio
147
+ async def test_handle_batch_json_rpc(self):
148
+ """Test handle_batch_json_rpc function."""
149
+ from mcp_proxy_adapter.api.handlers import handle_batch_json_rpc
150
+
151
+ # Create mock for handle_json_rpc
152
+ with patch("mcp_proxy_adapter.api.handlers.handle_json_rpc") as mock_handle_json_rpc:
153
+ # Setup mock responses
154
+ mock_handle_json_rpc.side_effect = [
155
+ {"jsonrpc": "2.0", "result": {"status": "success1"}, "id": "1"},
156
+ {"jsonrpc": "2.0", "result": {"status": "success2"}, "id": "2"}
157
+ ]
158
+
159
+ # Create batch request
160
+ batch_request = [
161
+ {"jsonrpc": "2.0", "method": "method1", "params": {}, "id": "1"},
162
+ {"jsonrpc": "2.0", "method": "method2", "params": {}, "id": "2"}
163
+ ]
164
+
165
+ # Create mock request
166
+ mock_request = MagicMock()
167
+ mock_request.state.request_id = "test-request-id"
168
+
169
+ # Call batch handler
170
+ responses = await handle_batch_json_rpc(batch_request, mock_request)
171
+
172
+ # Assertions
173
+ assert len(responses) == 2
174
+ assert responses[0]["jsonrpc"] == "2.0"
175
+ assert responses[0]["result"]["status"] == "success1"
176
+ assert responses[0]["id"] == "1"
177
+
178
+ assert responses[1]["jsonrpc"] == "2.0"
179
+ assert responses[1]["result"]["status"] == "success2"
180
+ assert responses[1]["id"] == "2"
181
+
182
+ # Check mock calls
183
+ assert mock_handle_json_rpc.call_count == 2
184
+ mock_handle_json_rpc.assert_any_call(batch_request[0], "test-request-id")
185
+ mock_handle_json_rpc.assert_any_call(batch_request[1], "test-request-id")
186
+
187
+
188
+ class TestCommandEndpoint:
189
+ """Tests for the /api/command/{command_name} endpoint."""
190
+
191
+ @patch("mcp_proxy_adapter.api.app.execute_command")
192
+ def test_command_endpoint_success(self, mock_execute_command, client, success_result):
193
+ """Test direct command execution with success."""
194
+ # Create mock params and result
195
+ mock_params = {"param": "value"}
196
+ mock_result = success_result.to_dict()
197
+
198
+ # Setup mock
199
+ mock_execute_command.return_value = mock_result
200
+
201
+ # Execute command
202
+ response = client.post("/api/command/test_command", json=mock_params)
203
+
204
+ # Assertions
205
+ assert response.status_code == 200
206
+ assert response.json() == mock_result
207
+ mock_execute_command.assert_called_once()
208
+
209
+ @patch("mcp_proxy_adapter.api.app.execute_command")
210
+ def test_command_endpoint_error(self, mock_execute_command, client):
211
+ """Test direct command execution with error."""
212
+ # Create mock error
213
+ from mcp_proxy_adapter.core.errors import MicroserviceError
214
+ mock_error = MicroserviceError("Test error", code=400)
215
+ mock_error_dict = {"error": {"code": 400, "message": "Test error"}}
216
+ mock_error.to_dict = MagicMock(return_value=mock_error_dict)
217
+
218
+ # Setup mock
219
+ mock_execute_command.side_effect = mock_error
220
+
221
+ # Execute command
222
+ response = client.post("/api/command/test_command", json={})
223
+
224
+ # Assertions
225
+ assert response.status_code == 400
226
+ assert response.json() == mock_error_dict
227
+ mock_execute_command.assert_called_once()
228
+
229
+
230
+ class TestCommandInfoEndpoint:
231
+ """Tests for command info endpoint."""
232
+
233
+ @patch("mcp_proxy_adapter.commands.command_registry.registry.get_command_info")
234
+ def test_command_info_endpoint_success(self, mock_get_command_info, client):
235
+ """Test command info endpoint returns command information."""
236
+ # Setup mock
237
+ mock_get_command_info.return_value = {
238
+ "name": "test_command",
239
+ "description": "Test command description",
240
+ "params": {"param1": {"type": "string"}},
241
+ "schema": {"properties": {"param1": {"type": "string"}}},
242
+ "result_schema": {"properties": {"key": {"type": "string"}}}
243
+ }
244
+
245
+ # Make request
246
+ response = client.get("/api/commands/test_command")
247
+
248
+ # Assertions
249
+ assert response.status_code == 200
250
+ data = response.json()
251
+ assert data["name"] == "test_command"
252
+ assert data["description"] == "Test command description"
253
+ assert "params" in data
254
+ assert "schema" in data
255
+ assert "result_schema" in data
256
+
257
+ @patch("mcp_proxy_adapter.commands.command_registry.registry.get_command_info")
258
+ def test_command_info_endpoint_not_found(self, mock_get_command_info, client):
259
+ """Test command info endpoint returns 404 for non-existent command."""
260
+ # Setup mock to raise NotFoundError
261
+ mock_get_command_info.side_effect = NotFoundError("Command not found")
262
+
263
+ # Make request
264
+ response = client.get("/api/commands/non_existent")
265
+
266
+ # Assertions
267
+ assert response.status_code == 404
268
+ data = response.json()
269
+ assert "error" in data
270
+ assert data["error"]["code"] == 404
271
+ assert "Command 'non_existent' not found" in data["error"]["message"]
@@ -0,0 +1,289 @@
1
+ """
2
+ Tests for API handlers.
3
+ """
4
+
5
+ import json
6
+ import pytest
7
+ from typing import Dict, Any
8
+ from unittest.mock import AsyncMock, patch, MagicMock
9
+
10
+ from mcp_proxy_adapter.api.handlers import (
11
+ execute_command, handle_json_rpc, handle_batch_json_rpc,
12
+ get_server_health, get_commands_list
13
+ )
14
+ from mcp_proxy_adapter.commands.result import SuccessResult, ErrorResult
15
+ from mcp_proxy_adapter.tests.stubs.echo_command import EchoResult
16
+ from mcp_proxy_adapter.core.errors import (
17
+ ValidationError, CommandError, NotFoundError, MethodNotFoundError,
18
+ InvalidRequestError, ParseError, InternalError
19
+ )
20
+
21
+
22
+ @pytest.fixture
23
+ def success_result():
24
+ """Fixture for test success result."""
25
+ result = SuccessResult(data={"key": "value"}, message="Success")
26
+ return result
27
+
28
+
29
+ @pytest.fixture
30
+ def error_result():
31
+ """Fixture for test error result."""
32
+ result = ErrorResult(message="Error message", code=400)
33
+ return result
34
+
35
+
36
+ class TestExecuteCommand:
37
+ """Tests for execute_command function."""
38
+
39
+ @pytest.mark.asyncio
40
+ async def test_execute_command_success(self):
41
+ """Test successful command execution."""
42
+ # Mock successful result
43
+ mock_result = EchoResult(params={"test_key": "test_value"})
44
+
45
+ # Mock command class and registry
46
+ with patch("mcp_proxy_adapter.commands.command_registry.registry.get_command") as mock_get_command:
47
+ # Создаем асинхронную mock-функцию
48
+ mock_run = AsyncMock(return_value=mock_result)
49
+ mock_command_class = MagicMock()
50
+ # Присваиваем асинхронную функцию методу run
51
+ mock_command_class.run = mock_run
52
+ mock_get_command.return_value = mock_command_class
53
+
54
+ # Execute command
55
+ result = await execute_command("test_command", {"param": "value"})
56
+
57
+ # Assert command was called correctly
58
+ mock_get_command.assert_called_once_with("test_command")
59
+ mock_run.assert_called_once_with(param="value")
60
+
61
+ # Assert result is as expected
62
+ assert result == mock_result.to_dict()
63
+
64
+ @pytest.mark.asyncio
65
+ async def test_execute_command_not_found(self):
66
+ """Test command not found error."""
67
+ # Mock registry raising NotFoundError
68
+ with patch("mcp_proxy_adapter.commands.command_registry.registry.get_command") as mock_get_command:
69
+ mock_get_command.side_effect = NotFoundError("Command not found")
70
+
71
+ # Execute command and expect MethodNotFoundError
72
+ with pytest.raises(MethodNotFoundError) as exc_info:
73
+ await execute_command("unknown_command", {})
74
+
75
+ # Check error message
76
+ assert "Method not found" in str(exc_info.value)
77
+
78
+ @pytest.mark.asyncio
79
+ async def test_execute_command_internal_error(self):
80
+ """Test internal error during command execution."""
81
+ # Mock registry raising an unexpected error
82
+ with patch("mcp_proxy_adapter.commands.command_registry.registry.get_command") as mock_get_command:
83
+ mock_get_command.side_effect = Exception("Unexpected error")
84
+
85
+ # Execute command and expect InternalError
86
+ with pytest.raises(InternalError) as exc_info:
87
+ await execute_command("test_command", {})
88
+
89
+ # Check error details
90
+ assert "Error executing command" in str(exc_info.value)
91
+ assert "original_error" in exc_info.value.data
92
+
93
+
94
+ class TestHandleJsonRpc:
95
+ """Tests for handle_json_rpc function."""
96
+
97
+ @pytest.mark.asyncio
98
+ async def test_handle_json_rpc_success(self):
99
+ """Test successful JSON-RPC request handling."""
100
+ # Mock execute_command
101
+ with patch("mcp_proxy_adapter.api.handlers.execute_command") as mock_execute:
102
+ # AsyncMock для асинхронной функции
103
+ mock_execute.return_value = {"result": "success"}
104
+
105
+ # Create request data
106
+ request_data = {
107
+ "jsonrpc": "2.0",
108
+ "method": "test_command",
109
+ "params": {"param": "value"},
110
+ "id": 123
111
+ }
112
+
113
+ # Handle request
114
+ response = await handle_json_rpc(request_data)
115
+
116
+ # Assert command was executed
117
+ mock_execute.assert_called_once_with("test_command", {"param": "value"}, None)
118
+
119
+ # Assert response format
120
+ assert response["jsonrpc"] == "2.0"
121
+ assert response["result"] == {"result": "success"}
122
+ assert response["id"] == 123
123
+
124
+ @pytest.mark.asyncio
125
+ async def test_handle_json_rpc_invalid_version(self):
126
+ """Test invalid JSON-RPC version."""
127
+ # Create request with invalid version
128
+ request_data = {
129
+ "jsonrpc": "1.0",
130
+ "method": "test_command",
131
+ "id": 123
132
+ }
133
+
134
+ # Handle request
135
+ response = await handle_json_rpc(request_data)
136
+
137
+ # Assert error response
138
+ assert response["jsonrpc"] == "2.0"
139
+ assert response["error"]["code"] == -32600
140
+ assert "Invalid Request" in response["error"]["message"]
141
+ assert response["id"] == 123
142
+
143
+ @pytest.mark.asyncio
144
+ async def test_handle_json_rpc_missing_method(self):
145
+ """Test missing method in JSON-RPC request."""
146
+ # Create request with missing method
147
+ request_data = {
148
+ "jsonrpc": "2.0",
149
+ "params": {},
150
+ "id": 123
151
+ }
152
+
153
+ # Handle request
154
+ response = await handle_json_rpc(request_data)
155
+
156
+ # Assert error response
157
+ assert response["jsonrpc"] == "2.0"
158
+ assert response["error"]["code"] == -32600
159
+ assert "Method is required" in response["error"]["message"]
160
+ assert response["id"] == 123
161
+
162
+ @pytest.mark.asyncio
163
+ async def test_handle_json_rpc_microservice_error(self):
164
+ """Test microservice error during command execution."""
165
+ # Mock execute_command raising MicroserviceError
166
+ with patch("mcp_proxy_adapter.api.handlers.execute_command") as mock_execute:
167
+ mock_execute.side_effect = CommandError("Command failed", data={"reason": "test"})
168
+
169
+ # Create request data
170
+ request_data = {
171
+ "jsonrpc": "2.0",
172
+ "method": "test_command",
173
+ "params": {},
174
+ "id": 123
175
+ }
176
+
177
+ # Handle request
178
+ response = await handle_json_rpc(request_data)
179
+
180
+ # Assert error response
181
+ assert response["jsonrpc"] == "2.0"
182
+ assert response["error"]["code"] == -32000
183
+ assert "Command failed" in response["error"]["message"]
184
+ assert response["error"]["data"]["reason"] == "test"
185
+ assert response["id"] == 123
186
+
187
+ @pytest.mark.asyncio
188
+ async def test_handle_json_rpc_unhandled_error(self):
189
+ """Test unhandled error during command execution."""
190
+ # Mock execute_command raising unexpected error
191
+ with patch("mcp_proxy_adapter.api.handlers.execute_command") as mock_execute:
192
+ mock_execute.side_effect = Exception("Unexpected error")
193
+
194
+ # Create request data
195
+ request_data = {
196
+ "jsonrpc": "2.0",
197
+ "method": "test_command",
198
+ "params": {},
199
+ "id": 123
200
+ }
201
+
202
+ # Handle request
203
+ response = await handle_json_rpc(request_data)
204
+
205
+ # Assert error response
206
+ assert response["jsonrpc"] == "2.0"
207
+ assert response["error"]["code"] == -32603
208
+ assert "Internal error" in response["error"]["message"]
209
+ assert "Unexpected error" in response["error"]["data"]["error"]
210
+ assert response["id"] == 123
211
+
212
+
213
+ class TestHandleBatchJsonRpc:
214
+ """Tests for handle_batch_json_rpc function."""
215
+
216
+ @pytest.mark.asyncio
217
+ async def test_handle_batch_json_rpc(self):
218
+ """Test batch JSON-RPC request handling."""
219
+ # Mock handle_json_rpc
220
+ with patch("mcp_proxy_adapter.api.handlers.handle_json_rpc") as mock_handle:
221
+ # AsyncMock для асинхронных результатов
222
+ mock_handle.side_effect = [
223
+ {"jsonrpc": "2.0", "result": "result1", "id": 1},
224
+ {"jsonrpc": "2.0", "result": "result2", "id": 2}
225
+ ]
226
+
227
+ # Create batch request
228
+ batch_requests = [
229
+ {"jsonrpc": "2.0", "method": "method1", "id": 1},
230
+ {"jsonrpc": "2.0", "method": "method2", "id": 2}
231
+ ]
232
+
233
+ # Handle batch request
234
+ responses = await handle_batch_json_rpc(batch_requests)
235
+
236
+ # Assert responses
237
+ assert len(responses) == 2
238
+ assert responses[0]["result"] == "result1"
239
+ assert responses[1]["result"] == "result2"
240
+
241
+
242
+ class TestGetServerHealth:
243
+ """Tests for get_server_health function."""
244
+
245
+ @pytest.mark.asyncio
246
+ async def test_get_server_health(self):
247
+ """Test getting server health information."""
248
+ # Call server health function
249
+ result = await get_server_health()
250
+
251
+ # Check basic structure and keys
252
+ assert "status" in result
253
+ assert result["status"] == "ok"
254
+ assert "version" in result
255
+ assert "uptime" in result
256
+ assert "components" in result
257
+ assert "system" in result["components"]
258
+ assert "process" in result["components"]
259
+ assert "commands" in result["components"]
260
+
261
+
262
+ class TestGetCommandsList:
263
+ """Tests for get_commands_list function."""
264
+
265
+ @pytest.mark.asyncio
266
+ async def test_get_commands_list(self):
267
+ """Test getting commands list."""
268
+ # Mock registry.get_all_commands
269
+ with patch("mcp_proxy_adapter.commands.command_registry.registry.get_all_commands") as mock_get_all:
270
+ # Create mock command class
271
+ mock_command = MagicMock()
272
+ mock_command.get_schema.return_value = {
273
+ "type": "object",
274
+ "description": "Test command description"
275
+ }
276
+
277
+ # Setup mock to return test commands
278
+ mock_get_all.return_value = {
279
+ "test_command": mock_command
280
+ }
281
+
282
+ # Call get_commands_list
283
+ result = await get_commands_list()
284
+
285
+ # Check result structure
286
+ assert "test_command" in result
287
+ assert result["test_command"]["name"] == "test_command"
288
+ assert result["test_command"]["description"] == "Test command description"
289
+ assert result["test_command"]["schema"]["type"] == "object"
@@ -0,0 +1,123 @@
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
+ def test_success_result():
53
+ """Test success result class."""
54
+ result = SuccessResult(data={"key": "value"}, message="success message")
55
+
56
+ # Test to_dict method
57
+ result_dict = result.to_dict()
58
+ assert result_dict["success"] is True
59
+ assert result_dict["data"] == {"key": "value"}
60
+ assert result_dict["message"] == "success message"
61
+
62
+ # Test from_dict method
63
+ result2 = SuccessResult.from_dict(result_dict)
64
+ assert result2.data == {"key": "value"}
65
+ assert result2.message == "success message"
66
+
67
+
68
+ def test_error_result():
69
+ """Test error result class."""
70
+ result = ErrorResult(message="error message", code=400, details={"field": "invalid"})
71
+
72
+ # Test to_dict method
73
+ result_dict = result.to_dict()
74
+ assert result_dict["success"] is False
75
+ assert result_dict["error"]["code"] == 400
76
+ assert result_dict["error"]["message"] == "error message"
77
+ assert result_dict["error"]["data"] == {"field": "invalid"}
78
+
79
+ # Test from_dict method
80
+ result2 = ErrorResult.from_dict(result_dict)
81
+ assert result2.message == "error message"
82
+ assert result2.code == 400
83
+ assert result2.details == {"field": "invalid"}
84
+
85
+
86
+ class TestCommandClass:
87
+ """Test command class."""
88
+
89
+ @pytest.mark.asyncio
90
+ async def test_execute(self):
91
+ """Test execute method."""
92
+ command = TestCommand()
93
+ result = await command.execute(value="test_value")
94
+ assert isinstance(result, MockResultClass)
95
+ assert result.value == "test_value"
96
+
97
+ @pytest.mark.asyncio
98
+ async def test_run(self):
99
+ """Test run method (with validation)."""
100
+ result = await TestCommand.run(value="test_value")
101
+ assert isinstance(result, MockResultClass)
102
+ assert result.value == "test_value"
103
+
104
+ def test_get_schema(self):
105
+ """Test get_schema method."""
106
+ schema = TestCommand.get_schema()
107
+ assert schema["type"] == "object"
108
+ assert "value" in schema["properties"]
109
+ assert schema["additionalProperties"] is False
110
+
111
+ def test_get_result_schema(self):
112
+ """Test get_result_schema method."""
113
+ schema = TestCommand.get_result_schema()
114
+ assert schema["type"] == "object"
115
+ assert "value" in schema["properties"]
116
+ assert "value" in schema["required"]
117
+
118
+ def test_get_param_info(self):
119
+ """Test get_param_info method."""
120
+ params = TestCommand.get_param_info()
121
+ assert "value" in params
122
+ assert params["value"]["required"] is False
123
+ assert params["value"]["default"] == "default"