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,138 @@
1
+ """
2
+ Module with utility functions for the microservice.
3
+ """
4
+
5
+ import hashlib
6
+ import json
7
+ import os
8
+ import sys
9
+ import time
10
+ import uuid
11
+ from datetime import datetime
12
+ from typing import Any, Dict, List, Optional, Tuple, Union
13
+
14
+ from mcp_proxy_adapter.core.logging import logger
15
+
16
+
17
+ def generate_id() -> str:
18
+ """
19
+ Generates a unique identifier.
20
+
21
+ Returns:
22
+ String with unique identifier.
23
+ """
24
+ return str(uuid.uuid4())
25
+
26
+
27
+ def get_timestamp() -> int:
28
+ """
29
+ Returns current timestamp in milliseconds.
30
+
31
+ Returns:
32
+ Integer - timestamp in milliseconds.
33
+ """
34
+ return int(time.time() * 1000)
35
+
36
+
37
+ def format_datetime(dt: Optional[datetime] = None, format_str: str = "%Y-%m-%dT%H:%M:%S.%fZ") -> str:
38
+ """
39
+ Formats date and time as string.
40
+
41
+ Args:
42
+ dt: Datetime object to format. If None, current time is used.
43
+ format_str: Format string for output.
44
+
45
+ Returns:
46
+ Formatted date/time string.
47
+ """
48
+ dt = dt or datetime.utcnow()
49
+ return dt.strftime(format_str)
50
+
51
+
52
+ def parse_datetime(dt_str: str, format_str: str = "%Y-%m-%dT%H:%M:%S.%fZ") -> datetime:
53
+ """
54
+ Parses date/time string into datetime object.
55
+
56
+ Args:
57
+ dt_str: Date/time string.
58
+ format_str: Date/time string format.
59
+
60
+ Returns:
61
+ Datetime object.
62
+ """
63
+ return datetime.strptime(dt_str, format_str)
64
+
65
+
66
+ def safe_json_loads(s: str, default: Any = None) -> Any:
67
+ """
68
+ Safe JSON string loading.
69
+
70
+ Args:
71
+ s: JSON string to load.
72
+ default: Default value on parsing error.
73
+
74
+ Returns:
75
+ Loaded object or default value on error.
76
+ """
77
+ try:
78
+ return json.loads(s)
79
+ except Exception as e:
80
+ logger.error(f"Error parsing JSON: {e}")
81
+ return default
82
+
83
+
84
+ def safe_json_dumps(obj: Any, default: str = "{}", indent: Optional[int] = None) -> str:
85
+ """
86
+ Safe object conversion to JSON string.
87
+
88
+ Args:
89
+ obj: Object to convert.
90
+ default: Default string on serialization error.
91
+ indent: Indentation for JSON formatting.
92
+
93
+ Returns:
94
+ JSON string or default string on error.
95
+ """
96
+ try:
97
+ return json.dumps(obj, ensure_ascii=False, indent=indent)
98
+ except Exception as e:
99
+ logger.error(f"Error serializing to JSON: {e}")
100
+ return default
101
+
102
+
103
+ def calculate_hash(data: Union[str, bytes], algorithm: str = "sha256") -> str:
104
+ """
105
+ Calculates hash for data.
106
+
107
+ Args:
108
+ data: Data to hash (string or bytes).
109
+ algorithm: Hashing algorithm (md5, sha1, sha256, etc.).
110
+
111
+ Returns:
112
+ String with hash in hexadecimal format.
113
+ """
114
+ if isinstance(data, str):
115
+ data = data.encode("utf-8")
116
+
117
+ hash_obj = hashlib.new(algorithm)
118
+ hash_obj.update(data)
119
+ return hash_obj.hexdigest()
120
+
121
+
122
+ def ensure_directory(path: str) -> bool:
123
+ """
124
+ Checks directory existence and creates it if necessary.
125
+
126
+ Args:
127
+ path: Path to directory.
128
+
129
+ Returns:
130
+ True if directory exists or was successfully created, otherwise False.
131
+ """
132
+ try:
133
+ if not os.path.exists(path):
134
+ os.makedirs(path, exist_ok=True)
135
+ return True
136
+ except Exception as e:
137
+ logger.error(f"Error creating directory {path}: {e}")
138
+ return False
File without changes
@@ -0,0 +1,114 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Base Schema",
4
+ "description": "Basic schema for validating commands and results",
5
+ "definitions": {
6
+ "command": {
7
+ "type": "object",
8
+ "properties": {
9
+ "jsonrpc": {
10
+ "type": "string",
11
+ "enum": ["2.0"],
12
+ "description": "JSON-RPC version"
13
+ },
14
+ "method": {
15
+ "type": "string",
16
+ "description": "Command name"
17
+ },
18
+ "params": {
19
+ "type": "object",
20
+ "description": "Command parameters"
21
+ },
22
+ "id": {
23
+ "oneOf": [
24
+ {"type": "string"},
25
+ {"type": "integer"},
26
+ {"type": "null"}
27
+ ],
28
+ "description": "Request ID"
29
+ }
30
+ },
31
+ "required": ["jsonrpc", "method"]
32
+ },
33
+ "success_response": {
34
+ "type": "object",
35
+ "properties": {
36
+ "jsonrpc": {
37
+ "type": "string",
38
+ "enum": ["2.0"],
39
+ "description": "JSON-RPC version"
40
+ },
41
+ "result": {
42
+ "type": "object",
43
+ "properties": {
44
+ "success": {
45
+ "type": "boolean",
46
+ "enum": [true],
47
+ "description": "Success flag"
48
+ },
49
+ "data": {
50
+ "type": "object",
51
+ "description": "Response data"
52
+ },
53
+ "message": {
54
+ "type": "string",
55
+ "description": "Response message"
56
+ }
57
+ },
58
+ "required": ["success"]
59
+ },
60
+ "id": {
61
+ "oneOf": [
62
+ {"type": "string"},
63
+ {"type": "integer"},
64
+ {"type": "null"}
65
+ ],
66
+ "description": "Request ID"
67
+ }
68
+ },
69
+ "required": ["jsonrpc", "result"]
70
+ },
71
+ "error_response": {
72
+ "type": "object",
73
+ "properties": {
74
+ "jsonrpc": {
75
+ "type": "string",
76
+ "enum": ["2.0"],
77
+ "description": "JSON-RPC version"
78
+ },
79
+ "error": {
80
+ "type": "object",
81
+ "properties": {
82
+ "code": {
83
+ "type": "integer",
84
+ "description": "Error code"
85
+ },
86
+ "message": {
87
+ "type": "string",
88
+ "description": "Error message"
89
+ },
90
+ "details": {
91
+ "type": "object",
92
+ "description": "Detailed error information"
93
+ }
94
+ },
95
+ "required": ["code", "message"]
96
+ },
97
+ "id": {
98
+ "oneOf": [
99
+ {"type": "string"},
100
+ {"type": "integer"},
101
+ {"type": "null"}
102
+ ],
103
+ "description": "Request ID"
104
+ }
105
+ },
106
+ "required": ["jsonrpc", "error"]
107
+ }
108
+ },
109
+ "oneOf": [
110
+ {"$ref": "#/definitions/command"},
111
+ {"$ref": "#/definitions/success_response"},
112
+ {"$ref": "#/definitions/error_response"}
113
+ ]
114
+ }
@@ -0,0 +1,314 @@
1
+ {
2
+ "openapi": "3.0.2",
3
+ "info": {
4
+ "title": "MCP Microservice API",
5
+ "description": "API для выполнения команд микросервиса",
6
+ "version": "1.0.0"
7
+ },
8
+ "paths": {
9
+ "/cmd": {
10
+ "post": {
11
+ "summary": "Execute Command",
12
+ "description": "Executes a command via JSON-RPC protocol.",
13
+ "operationId": "execute_command",
14
+ "requestBody": {
15
+ "content": {
16
+ "application/json": {
17
+ "schema": {
18
+ "oneOf": [
19
+ { "$ref": "#/components/schemas/CommandRequest" },
20
+ { "$ref": "#/components/schemas/JsonRpcRequest" }
21
+ ]
22
+ }
23
+ }
24
+ },
25
+ "required": true
26
+ },
27
+ "responses": {
28
+ "200": {
29
+ "description": "Successful Response",
30
+ "content": {
31
+ "application/json": {
32
+ "schema": {
33
+ "oneOf": [
34
+ { "$ref": "#/components/schemas/CommandResponse" },
35
+ { "$ref": "#/components/schemas/JsonRpcResponse" }
36
+ ]
37
+ }
38
+ }
39
+ }
40
+ },
41
+ "422": {
42
+ "description": "Validation Error",
43
+ "content": {
44
+ "application/json": {
45
+ "schema": {
46
+ "$ref": "#/components/schemas/HTTPValidationError"
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ },
54
+ "/health": {
55
+ "get": {
56
+ "summary": "Проверить работоспособность сервиса",
57
+ "description": "Возвращает информацию о состоянии сервиса",
58
+ "operationId": "health_check",
59
+ "responses": {
60
+ "200": {
61
+ "description": "Информация о состоянии сервиса",
62
+ "content": {
63
+ "application/json": {
64
+ "schema": {
65
+ "$ref": "#/components/schemas/HealthResponse"
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ },
73
+ "/openapi.json": {
74
+ "get": {
75
+ "summary": "Get Openapi Schema",
76
+ "description": "Returns OpenAPI schema.",
77
+ "operationId": "get_openapi_schema_openapi_json_get",
78
+ "responses": {
79
+ "200": {
80
+ "description": "Successful Response",
81
+ "content": {
82
+ "application/json": {
83
+ "schema": {}
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ },
90
+ "/api/commands": {
91
+ "get": {
92
+ "summary": "Get Commands",
93
+ "description": "Returns list of available commands with their descriptions.",
94
+ "operationId": "get_commands_api_commands_get",
95
+ "responses": {
96
+ "200": {
97
+ "description": "Successful Response",
98
+ "content": {
99
+ "application/json": {
100
+ "schema": {}
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+ },
108
+ "components": {
109
+ "schemas": {
110
+ "CommandRequest": {
111
+ "title": "CommandRequest",
112
+ "description": "Запрос на выполнение команды",
113
+ "type": "object",
114
+ "required": [
115
+ "command"
116
+ ],
117
+ "properties": {
118
+ "command": {
119
+ "title": "Command",
120
+ "description": "Команда для выполнения",
121
+ "type": "string"
122
+ },
123
+ "params": {
124
+ "title": "Parameters",
125
+ "description": "Параметры команды, зависят от типа команды",
126
+ "type": "object",
127
+ "additionalProperties": true
128
+ }
129
+ }
130
+ },
131
+ "CommandResponse": {
132
+ "title": "CommandResponse",
133
+ "description": "Ответ на выполнение команды",
134
+ "type": "object",
135
+ "required": [
136
+ "result"
137
+ ],
138
+ "properties": {
139
+ "result": {
140
+ "title": "Result",
141
+ "description": "Результат выполнения команды"
142
+ }
143
+ }
144
+ },
145
+ "JsonRpcRequest": {
146
+ "properties": {
147
+ "jsonrpc": {
148
+ "type": "string",
149
+ "title": "Jsonrpc",
150
+ "description": "JSON-RPC version",
151
+ "default": "2.0"
152
+ },
153
+ "method": {
154
+ "type": "string",
155
+ "title": "Method",
156
+ "description": "Method name to call"
157
+ },
158
+ "params": {
159
+ "additionalProperties": true,
160
+ "type": "object",
161
+ "title": "Params",
162
+ "description": "Method parameters",
163
+ "default": {}
164
+ },
165
+ "id": {
166
+ "anyOf": [
167
+ {
168
+ "type": "string"
169
+ },
170
+ {
171
+ "type": "integer"
172
+ },
173
+ {
174
+ "type": "null"
175
+ }
176
+ ],
177
+ "title": "Id",
178
+ "description": "Request identifier"
179
+ }
180
+ },
181
+ "type": "object",
182
+ "required": [
183
+ "method"
184
+ ],
185
+ "title": "JsonRpcRequest",
186
+ "description": "Base model for JSON-RPC requests."
187
+ },
188
+ "JsonRpcResponse": {
189
+ "properties": {
190
+ "jsonrpc": {
191
+ "type": "string",
192
+ "title": "Jsonrpc",
193
+ "description": "JSON-RPC version",
194
+ "default": "2.0"
195
+ },
196
+ "result": {
197
+ "anyOf": [
198
+ {},
199
+ {
200
+ "type": "null"
201
+ }
202
+ ],
203
+ "title": "Result",
204
+ "description": "Method execution result"
205
+ },
206
+ "error": {
207
+ "anyOf": [
208
+ {
209
+ "additionalProperties": true,
210
+ "type": "object"
211
+ },
212
+ {
213
+ "type": "null"
214
+ }
215
+ ],
216
+ "title": "Error",
217
+ "description": "Error information"
218
+ },
219
+ "id": {
220
+ "anyOf": [
221
+ {
222
+ "type": "string"
223
+ },
224
+ {
225
+ "type": "integer"
226
+ },
227
+ {
228
+ "type": "null"
229
+ }
230
+ ],
231
+ "title": "Id",
232
+ "description": "Request identifier"
233
+ }
234
+ },
235
+ "type": "object",
236
+ "title": "JsonRpcResponse",
237
+ "description": "Base model for JSON-RPC responses."
238
+ },
239
+ "HealthResponse": {
240
+ "title": "HealthResponse",
241
+ "description": "Информация о состоянии сервиса",
242
+ "type": "object",
243
+ "required": [
244
+ "status",
245
+ "model",
246
+ "version"
247
+ ],
248
+ "properties": {
249
+ "status": {
250
+ "title": "Status",
251
+ "description": "Статус сервиса (ok/error)",
252
+ "type": "string"
253
+ },
254
+ "model": {
255
+ "title": "Model",
256
+ "description": "Текущая активная модель",
257
+ "type": "string"
258
+ },
259
+ "version": {
260
+ "title": "Version",
261
+ "description": "Версия сервиса",
262
+ "type": "string"
263
+ }
264
+ }
265
+ },
266
+ "HTTPValidationError": {
267
+ "properties": {
268
+ "detail": {
269
+ "items": {
270
+ "$ref": "#/components/schemas/ValidationError"
271
+ },
272
+ "type": "array",
273
+ "title": "Detail"
274
+ }
275
+ },
276
+ "type": "object",
277
+ "title": "HTTPValidationError"
278
+ },
279
+ "ValidationError": {
280
+ "properties": {
281
+ "loc": {
282
+ "items": {
283
+ "anyOf": [
284
+ {
285
+ "type": "string"
286
+ },
287
+ {
288
+ "type": "integer"
289
+ }
290
+ ]
291
+ },
292
+ "type": "array",
293
+ "title": "Location"
294
+ },
295
+ "msg": {
296
+ "type": "string",
297
+ "title": "Message"
298
+ },
299
+ "type": {
300
+ "type": "string",
301
+ "title": "Error Type"
302
+ }
303
+ },
304
+ "type": "object",
305
+ "required": [
306
+ "loc",
307
+ "msg",
308
+ "type"
309
+ ],
310
+ "title": "ValidationError"
311
+ }
312
+ }
313
+ }
314
+ }
File without changes
@@ -0,0 +1,3 @@
1
+ """
2
+ API tests package.
3
+ """
@@ -0,0 +1,115 @@
1
+ """
2
+ Tests for the /cmd endpoint.
3
+ """
4
+
5
+ import pytest
6
+ from unittest.mock import patch, MagicMock, ANY
7
+ from fastapi.testclient import TestClient
8
+
9
+ from mcp_proxy_adapter.api.app import app
10
+ from mcp_proxy_adapter.commands.command_registry import registry
11
+ from mcp_proxy_adapter.core.errors import MicroserviceError
12
+
13
+
14
+ @pytest.fixture
15
+ def client():
16
+ """Test client for FastAPI app."""
17
+ return TestClient(app)
18
+
19
+
20
+ @pytest.fixture
21
+ def mock_registry():
22
+ """Mock for command registry."""
23
+ with patch("mcp_proxy_adapter.api.app.registry") as mock_reg:
24
+ yield mock_reg
25
+
26
+
27
+ @pytest.fixture
28
+ def mock_execute_command():
29
+ """Mock for execute_command function."""
30
+ with patch("mcp_proxy_adapter.api.app.execute_command") as mock_exec:
31
+ yield mock_exec
32
+
33
+
34
+ def test_cmd_endpoint_basic(client, mock_registry, mock_execute_command):
35
+ """Test basic execution of /cmd endpoint."""
36
+ # Setup mocks
37
+ mock_registry.command_exists.return_value = True
38
+ mock_execute_command.return_value = {"key": "value"}
39
+
40
+ # Send request
41
+ response = client.post(
42
+ "/cmd",
43
+ json={"command": "test_command", "params": {"param1": "value1"}}
44
+ )
45
+
46
+ # Check result
47
+ assert response.status_code == 200
48
+ assert response.json() == {"result": {"key": "value"}}
49
+
50
+ # Verify mock calls with ANY for request_id since it can be dynamic
51
+ mock_registry.command_exists.assert_called_once_with("test_command")
52
+ mock_execute_command.assert_called_once_with(
53
+ "test_command", {"param1": "value1"}, ANY
54
+ )
55
+
56
+
57
+ def test_cmd_endpoint_missing_command(client):
58
+ """Test /cmd endpoint with missing 'command' field."""
59
+ response = client.post("/cmd", json={})
60
+
61
+ assert response.status_code == 200
62
+ assert "error" in response.json()
63
+ assert response.json()["error"]["code"] == -32600
64
+ assert "Отсутствует обязательное поле 'command'" in response.json()["error"]["message"]
65
+
66
+
67
+ def test_cmd_endpoint_command_not_found(client, mock_registry):
68
+ """Test /cmd endpoint with non-existent command."""
69
+ # Setup mocks
70
+ mock_registry.command_exists.return_value = False
71
+
72
+ # Send request
73
+ response = client.post("/cmd", json={"command": "non_existent"})
74
+
75
+ # Check result
76
+ assert response.status_code == 200
77
+ assert "error" in response.json()
78
+ assert response.json()["error"]["code"] == -32601
79
+ assert "не найдена" in response.json()["error"]["message"]
80
+
81
+
82
+ def test_cmd_endpoint_error_handling(client, mock_registry, mock_execute_command):
83
+ """Test error handling in /cmd endpoint."""
84
+ # Setup mocks
85
+ mock_registry.command_exists.return_value = True
86
+
87
+ error = MicroserviceError("Test error", code=-32000)
88
+ error.to_dict = MagicMock(return_value={"code": -32000, "message": "Test error"})
89
+ mock_execute_command.side_effect = error
90
+
91
+ # Send request
92
+ response = client.post("/cmd", json={"command": "test_command"})
93
+
94
+ # Check result
95
+ assert response.status_code == 200
96
+ assert "error" in response.json()
97
+ assert response.json()["error"]["code"] == -32000
98
+ assert response.json()["error"]["message"] == "Test error"
99
+
100
+
101
+ def test_cmd_endpoint_internal_error(client, mock_registry, mock_execute_command):
102
+ """Test internal error handling in /cmd endpoint."""
103
+ # Setup mocks
104
+ mock_registry.command_exists.return_value = True
105
+ mock_execute_command.side_effect = Exception("Unexpected error")
106
+
107
+ # Send request
108
+ response = client.post("/cmd", json={"command": "test_command"})
109
+
110
+ # Check result
111
+ assert response.status_code == 200
112
+ assert "error" in response.json()
113
+ assert response.json()["error"]["code"] == -32603
114
+ assert "Internal error" in response.json()["error"]["message"]
115
+ assert "Unexpected error" in response.json()["error"]["data"]["details"]