mcp-proxy-adapter 3.1.6__py3-none-any.whl → 4.1.0__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 (118) hide show
  1. mcp_proxy_adapter/api/app.py +65 -27
  2. mcp_proxy_adapter/api/handlers.py +1 -1
  3. mcp_proxy_adapter/api/middleware/error_handling.py +11 -10
  4. mcp_proxy_adapter/api/tool_integration.py +5 -2
  5. mcp_proxy_adapter/api/tools.py +3 -3
  6. mcp_proxy_adapter/commands/base.py +19 -1
  7. mcp_proxy_adapter/commands/command_registry.py +254 -8
  8. mcp_proxy_adapter/commands/hooks.py +260 -0
  9. mcp_proxy_adapter/commands/reload_command.py +211 -0
  10. mcp_proxy_adapter/commands/reload_settings_command.py +125 -0
  11. mcp_proxy_adapter/commands/settings_command.py +189 -0
  12. mcp_proxy_adapter/config.py +16 -1
  13. mcp_proxy_adapter/core/__init__.py +44 -0
  14. mcp_proxy_adapter/core/logging.py +87 -34
  15. mcp_proxy_adapter/core/settings.py +376 -0
  16. mcp_proxy_adapter/core/utils.py +2 -2
  17. mcp_proxy_adapter/custom_openapi.py +81 -2
  18. mcp_proxy_adapter/examples/README.md +124 -0
  19. mcp_proxy_adapter/examples/__init__.py +7 -0
  20. mcp_proxy_adapter/examples/basic_server/README.md +60 -0
  21. mcp_proxy_adapter/examples/basic_server/__init__.py +7 -0
  22. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +39 -0
  23. mcp_proxy_adapter/examples/basic_server/config.json +35 -0
  24. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +238 -0
  25. mcp_proxy_adapter/examples/basic_server/server.py +98 -0
  26. mcp_proxy_adapter/examples/custom_commands/README.md +127 -0
  27. mcp_proxy_adapter/examples/custom_commands/__init__.py +27 -0
  28. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +250 -0
  29. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +6 -0
  30. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +103 -0
  31. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +111 -0
  32. mcp_proxy_adapter/examples/custom_commands/config.json +62 -0
  33. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +169 -0
  34. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +215 -0
  35. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +76 -0
  36. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +96 -0
  37. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +241 -0
  38. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +135 -0
  39. mcp_proxy_adapter/examples/custom_commands/echo_command.py +122 -0
  40. mcp_proxy_adapter/examples/custom_commands/hooks.py +230 -0
  41. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +123 -0
  42. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +103 -0
  43. mcp_proxy_adapter/examples/custom_commands/server.py +223 -0
  44. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +176 -0
  45. mcp_proxy_adapter/examples/deployment/README.md +49 -0
  46. mcp_proxy_adapter/examples/deployment/__init__.py +7 -0
  47. mcp_proxy_adapter/examples/deployment/config.development.json +8 -0
  48. {examples/basic_example → mcp_proxy_adapter/examples/deployment}/config.json +11 -7
  49. mcp_proxy_adapter/examples/deployment/config.production.json +12 -0
  50. mcp_proxy_adapter/examples/deployment/config.staging.json +11 -0
  51. mcp_proxy_adapter/examples/deployment/docker-compose.yml +31 -0
  52. mcp_proxy_adapter/examples/deployment/run.sh +43 -0
  53. mcp_proxy_adapter/examples/deployment/run_docker.sh +84 -0
  54. mcp_proxy_adapter/openapi.py +3 -2
  55. mcp_proxy_adapter/tests/api/test_custom_openapi.py +617 -0
  56. mcp_proxy_adapter/tests/api/test_handlers.py +522 -0
  57. mcp_proxy_adapter/tests/api/test_schemas.py +546 -0
  58. mcp_proxy_adapter/tests/api/test_tool_integration.py +531 -0
  59. mcp_proxy_adapter/tests/unit/test_base_command.py +391 -85
  60. mcp_proxy_adapter/version.py +1 -1
  61. {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.1.0.dist-info}/METADATA +3 -3
  62. mcp_proxy_adapter-4.1.0.dist-info/RECORD +110 -0
  63. {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.1.0.dist-info}/WHEEL +1 -1
  64. {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.1.0.dist-info}/top_level.txt +0 -1
  65. examples/__init__.py +0 -19
  66. examples/anti_patterns/README.md +0 -51
  67. examples/anti_patterns/__init__.py +0 -9
  68. examples/anti_patterns/bad_design/README.md +0 -72
  69. examples/anti_patterns/bad_design/global_state.py +0 -170
  70. examples/anti_patterns/bad_design/monolithic_command.py +0 -272
  71. examples/basic_example/README.md +0 -245
  72. examples/basic_example/__init__.py +0 -8
  73. examples/basic_example/commands/__init__.py +0 -5
  74. examples/basic_example/commands/echo_command.py +0 -95
  75. examples/basic_example/commands/math_command.py +0 -151
  76. examples/basic_example/commands/time_command.py +0 -152
  77. examples/basic_example/docs/EN/README.md +0 -177
  78. examples/basic_example/docs/RU/README.md +0 -177
  79. examples/basic_example/server.py +0 -151
  80. examples/basic_example/tests/conftest.py +0 -243
  81. examples/check_vstl_schema.py +0 -106
  82. examples/commands/echo_command.py +0 -52
  83. examples/commands/echo_command_di.py +0 -152
  84. examples/commands/echo_result.py +0 -65
  85. examples/commands/get_date_command.py +0 -98
  86. examples/commands/new_uuid4_command.py +0 -91
  87. examples/complete_example/Dockerfile +0 -24
  88. examples/complete_example/README.md +0 -92
  89. examples/complete_example/__init__.py +0 -8
  90. examples/complete_example/commands/__init__.py +0 -5
  91. examples/complete_example/commands/system_command.py +0 -328
  92. examples/complete_example/config.json +0 -41
  93. examples/complete_example/configs/config.dev.yaml +0 -40
  94. examples/complete_example/configs/config.docker.yaml +0 -40
  95. examples/complete_example/docker-compose.yml +0 -35
  96. examples/complete_example/requirements.txt +0 -20
  97. examples/complete_example/server.py +0 -113
  98. examples/di_example/.pytest_cache/README.md +0 -8
  99. examples/di_example/server.py +0 -249
  100. examples/fix_vstl_help.py +0 -123
  101. examples/minimal_example/README.md +0 -65
  102. examples/minimal_example/__init__.py +0 -8
  103. examples/minimal_example/config.json +0 -14
  104. examples/minimal_example/main.py +0 -136
  105. examples/minimal_example/simple_server.py +0 -163
  106. examples/minimal_example/tests/conftest.py +0 -171
  107. examples/minimal_example/tests/test_hello_command.py +0 -111
  108. examples/minimal_example/tests/test_integration.py +0 -181
  109. examples/patch_vstl_service.py +0 -105
  110. examples/patch_vstl_service_mcp.py +0 -108
  111. examples/server.py +0 -69
  112. examples/simple_server.py +0 -128
  113. examples/test_package_3.1.4.py +0 -177
  114. examples/test_server.py +0 -134
  115. examples/tool_description_example.py +0 -82
  116. mcp_proxy_adapter/py.typed +0 -0
  117. mcp_proxy_adapter-3.1.6.dist-info/RECORD +0 -118
  118. {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,546 @@
1
+ """
2
+ Tests for schemas module.
3
+
4
+ This module contains comprehensive tests for API schema definitions
5
+ to ensure 90%+ code coverage.
6
+ """
7
+
8
+ import pytest
9
+ import json
10
+ from typing import Dict, Any
11
+
12
+ from mcp_proxy_adapter.api.schemas import (
13
+ ErrorResponse, ErrorWrapper, JsonRpcRequest, JsonRpcError,
14
+ JsonRpcSuccessResponse, JsonRpcErrorResponse, CommandResponse,
15
+ HealthResponse, CommandListResponse, CommandRequest,
16
+ CommandSuccessResponse, CommandErrorResponse, APIToolDescription
17
+ )
18
+
19
+
20
+ class TestErrorResponse:
21
+ """Test cases for ErrorResponse model."""
22
+
23
+ def test_error_response_creation(self):
24
+ """Test ErrorResponse creation with all fields."""
25
+ error = ErrorResponse(
26
+ code=404,
27
+ message="Not found",
28
+ details={"resource": "user", "id": 123}
29
+ )
30
+
31
+ assert error.code == 404
32
+ assert error.message == "Not found"
33
+ assert error.details == {"resource": "user", "id": 123}
34
+
35
+ def test_error_response_creation_without_details(self):
36
+ """Test ErrorResponse creation without optional details."""
37
+ error = ErrorResponse(code=500, message="Internal server error")
38
+
39
+ assert error.code == 500
40
+ assert error.message == "Internal server error"
41
+ assert error.details is None
42
+
43
+ def test_error_response_to_dict(self):
44
+ """Test ErrorResponse serialization to dict."""
45
+ error = ErrorResponse(
46
+ code=400,
47
+ message="Bad request",
48
+ details={"field": "email"}
49
+ )
50
+
51
+ error_dict = error.model_dump()
52
+ assert error_dict["code"] == 400
53
+ assert error_dict["message"] == "Bad request"
54
+ assert error_dict["details"] == {"field": "email"}
55
+
56
+
57
+ class TestErrorWrapper:
58
+ """Test cases for ErrorWrapper model."""
59
+
60
+ def test_error_wrapper_creation(self):
61
+ """Test ErrorWrapper creation."""
62
+ error_response = ErrorResponse(code=404, message="Not found")
63
+ wrapper = ErrorWrapper(error=error_response)
64
+
65
+ assert wrapper.error == error_response
66
+ assert wrapper.error.code == 404
67
+
68
+ def test_error_wrapper_to_dict(self):
69
+ """Test ErrorWrapper serialization to dict."""
70
+ error_response = ErrorResponse(code=500, message="Server error")
71
+ wrapper = ErrorWrapper(error=error_response)
72
+
73
+ wrapper_dict = wrapper.model_dump()
74
+ assert "error" in wrapper_dict
75
+ assert wrapper_dict["error"]["code"] == 500
76
+
77
+
78
+ class TestJsonRpcRequest:
79
+ """Test cases for JsonRpcRequest model."""
80
+
81
+ def test_json_rpc_request_creation(self):
82
+ """Test JsonRpcRequest creation with all fields."""
83
+ request = JsonRpcRequest(
84
+ method="test_method",
85
+ params={"param1": "value1", "param2": 42},
86
+ id="request_123"
87
+ )
88
+
89
+ assert request.jsonrpc == "2.0"
90
+ assert request.method == "test_method"
91
+ assert request.params == {"param1": "value1", "param2": 42}
92
+ assert request.id == "request_123"
93
+
94
+ def test_json_rpc_request_creation_without_params(self):
95
+ """Test JsonRpcRequest creation without params."""
96
+ request = JsonRpcRequest(method="simple_method", id=1)
97
+
98
+ assert request.method == "simple_method"
99
+ assert request.params is None
100
+ assert request.id == 1
101
+
102
+ def test_json_rpc_request_creation_with_list_params(self):
103
+ """Test JsonRpcRequest creation with list params."""
104
+ request = JsonRpcRequest(
105
+ method="list_method",
106
+ params=["item1", "item2", "item3"],
107
+ id="list_request"
108
+ )
109
+
110
+ assert request.params == ["item1", "item2", "item3"]
111
+
112
+ def test_json_rpc_request_to_dict(self):
113
+ """Test JsonRpcRequest serialization to dict."""
114
+ request = JsonRpcRequest(
115
+ method="test_method",
116
+ params={"test": "value"},
117
+ id="test_id"
118
+ )
119
+
120
+ request_dict = request.model_dump()
121
+ assert request_dict["jsonrpc"] == "2.0"
122
+ assert request_dict["method"] == "test_method"
123
+ assert request_dict["params"] == {"test": "value"}
124
+ assert request_dict["id"] == "test_id"
125
+
126
+
127
+ class TestJsonRpcError:
128
+ """Test cases for JsonRpcError model."""
129
+
130
+ def test_json_rpc_error_creation(self):
131
+ """Test JsonRpcError creation with all fields."""
132
+ error = JsonRpcError(
133
+ code=-32601,
134
+ message="Method not found",
135
+ data={"available_methods": ["help", "config"]}
136
+ )
137
+
138
+ assert error.code == -32601
139
+ assert error.message == "Method not found"
140
+ assert error.data == {"available_methods": ["help", "config"]}
141
+
142
+ def test_json_rpc_error_creation_without_data(self):
143
+ """Test JsonRpcError creation without optional data."""
144
+ error = JsonRpcError(code=-32700, message="Parse error")
145
+
146
+ assert error.code == -32700
147
+ assert error.message == "Parse error"
148
+ assert error.data is None
149
+
150
+ def test_json_rpc_error_to_dict(self):
151
+ """Test JsonRpcError serialization to dict."""
152
+ error = JsonRpcError(
153
+ code=-32602,
154
+ message="Invalid params",
155
+ data={"expected": "string", "received": "number"}
156
+ )
157
+
158
+ error_dict = error.model_dump()
159
+ assert error_dict["code"] == -32602
160
+ assert error_dict["message"] == "Invalid params"
161
+ assert error_dict["data"] == {"expected": "string", "received": "number"}
162
+
163
+
164
+ class TestJsonRpcSuccessResponse:
165
+ """Test cases for JsonRpcSuccessResponse model."""
166
+
167
+ def test_json_rpc_success_response_creation(self):
168
+ """Test JsonRpcSuccessResponse creation."""
169
+ response = JsonRpcSuccessResponse(
170
+ result={"status": "success", "data": "test_data"},
171
+ id="response_123"
172
+ )
173
+
174
+ assert response.jsonrpc == "2.0"
175
+ assert response.result == {"status": "success", "data": "test_data"}
176
+ assert response.id == "response_123"
177
+
178
+ def test_json_rpc_success_response_to_dict(self):
179
+ """Test JsonRpcSuccessResponse serialization to dict."""
180
+ response = JsonRpcSuccessResponse(
181
+ result={"message": "OK"},
182
+ id=1
183
+ )
184
+
185
+ response_dict = response.model_dump()
186
+ assert response_dict["jsonrpc"] == "2.0"
187
+ assert response_dict["result"] == {"message": "OK"}
188
+ assert response_dict["id"] == 1
189
+
190
+
191
+ class TestJsonRpcErrorResponse:
192
+ """Test cases for JsonRpcErrorResponse model."""
193
+
194
+ def test_json_rpc_error_response_creation(self):
195
+ """Test JsonRpcErrorResponse creation."""
196
+ error = JsonRpcError(code=-32601, message="Method not found")
197
+ response = JsonRpcErrorResponse(error=error, id="error_response")
198
+
199
+ assert response.jsonrpc == "2.0"
200
+ assert response.error == error
201
+ assert response.id == "error_response"
202
+
203
+ def test_json_rpc_error_response_to_dict(self):
204
+ """Test JsonRpcErrorResponse serialization to dict."""
205
+ error = JsonRpcError(code=-32700, message="Parse error")
206
+ response = JsonRpcErrorResponse(error=error, id=1)
207
+
208
+ response_dict = response.model_dump()
209
+ assert response_dict["jsonrpc"] == "2.0"
210
+ assert response_dict["error"]["code"] == -32700
211
+ assert response_dict["error"]["message"] == "Parse error"
212
+ assert response_dict["id"] == 1
213
+
214
+
215
+ class TestCommandResponse:
216
+ """Test cases for CommandResponse model."""
217
+
218
+ def test_command_response_success(self):
219
+ """Test CommandResponse creation for success case."""
220
+ response = CommandResponse(
221
+ success=True,
222
+ data={"result": "command executed"},
223
+ message="Command completed successfully"
224
+ )
225
+
226
+ assert response.success is True
227
+ assert response.data == {"result": "command executed"}
228
+ assert response.message == "Command completed successfully"
229
+ assert response.error is None
230
+
231
+ def test_command_response_error(self):
232
+ """Test CommandResponse creation for error case."""
233
+ error = ErrorResponse(code=400, message="Bad request")
234
+ response = CommandResponse(
235
+ success=False,
236
+ error=error,
237
+ message="Command failed"
238
+ )
239
+
240
+ assert response.success is False
241
+ assert response.data is None
242
+ assert response.message == "Command failed"
243
+ assert response.error == error
244
+
245
+ def test_command_response_to_dict(self):
246
+ """Test CommandResponse serialization to dict."""
247
+ response = CommandResponse(
248
+ success=True,
249
+ data={"status": "ok"},
250
+ message="Success"
251
+ )
252
+
253
+ response_dict = response.model_dump()
254
+ assert response_dict["success"] is True
255
+ assert response_dict["data"] == {"status": "ok"}
256
+ assert response_dict["message"] == "Success"
257
+ assert response_dict["error"] is None
258
+
259
+
260
+ class TestHealthResponse:
261
+ """Test cases for HealthResponse model."""
262
+
263
+ def test_health_response_creation(self):
264
+ """Test HealthResponse creation."""
265
+ response = HealthResponse(
266
+ status="healthy",
267
+ version="1.0.0",
268
+ uptime=3600.5,
269
+ components={"database": "ok", "cache": "ok"}
270
+ )
271
+
272
+ assert response.status == "healthy"
273
+ assert response.version == "1.0.0"
274
+ assert response.uptime == 3600.5
275
+ assert response.components == {"database": "ok", "cache": "ok"}
276
+
277
+ def test_health_response_to_dict(self):
278
+ """Test HealthResponse serialization to dict."""
279
+ response = HealthResponse(
280
+ status="degraded",
281
+ version="2.1.0",
282
+ uptime=7200.0,
283
+ components={"database": "ok", "cache": "error"}
284
+ )
285
+
286
+ response_dict = response.model_dump()
287
+ assert response_dict["status"] == "degraded"
288
+ assert response_dict["version"] == "2.1.0"
289
+ assert response_dict["uptime"] == 7200.0
290
+ assert response_dict["components"] == {"database": "ok", "cache": "error"}
291
+
292
+
293
+ class TestCommandListResponse:
294
+ """Test cases for CommandListResponse model."""
295
+
296
+ def test_command_list_response_creation(self):
297
+ """Test CommandListResponse creation."""
298
+ commands = {
299
+ "help": {"summary": "Show help", "params": {}},
300
+ "config": {"summary": "Get config", "params": {"section": "string"}}
301
+ }
302
+ response = CommandListResponse(commands=commands)
303
+
304
+ assert response.commands == commands
305
+ assert len(response.commands) == 2
306
+
307
+ def test_command_list_response_to_dict(self):
308
+ """Test CommandListResponse serialization to dict."""
309
+ commands = {"test": {"summary": "Test command"}}
310
+ response = CommandListResponse(commands=commands)
311
+
312
+ response_dict = response.model_dump()
313
+ assert response_dict["commands"] == commands
314
+
315
+
316
+ class TestCommandRequest:
317
+ """Test cases for CommandRequest model."""
318
+
319
+ def test_command_request_creation(self):
320
+ """Test CommandRequest creation with params."""
321
+ request = CommandRequest(
322
+ command="test_command",
323
+ params={"param1": "value1", "param2": 42}
324
+ )
325
+
326
+ assert request.command == "test_command"
327
+ assert request.params == {"param1": "value1", "param2": 42}
328
+
329
+ def test_command_request_creation_without_params(self):
330
+ """Test CommandRequest creation without params."""
331
+ request = CommandRequest(command="simple_command")
332
+
333
+ assert request.command == "simple_command"
334
+ assert request.params == {}
335
+
336
+ def test_command_request_to_dict(self):
337
+ """Test CommandRequest serialization to dict."""
338
+ request = CommandRequest(
339
+ command="test_command",
340
+ params={"test": "value"}
341
+ )
342
+
343
+ request_dict = request.model_dump()
344
+ assert request_dict["command"] == "test_command"
345
+ assert request_dict["params"] == {"test": "value"}
346
+
347
+
348
+ class TestCommandSuccessResponse:
349
+ """Test cases for CommandSuccessResponse model."""
350
+
351
+ def test_command_success_response_creation(self):
352
+ """Test CommandSuccessResponse creation."""
353
+ response = CommandSuccessResponse(
354
+ result={"status": "success", "data": "test_data"}
355
+ )
356
+
357
+ assert response.result == {"status": "success", "data": "test_data"}
358
+
359
+ def test_command_success_response_with_id(self):
360
+ """Test CommandSuccessResponse creation with id."""
361
+ response = CommandSuccessResponse(
362
+ result={"message": "OK"}
363
+ )
364
+
365
+ assert response.result == {"message": "OK"}
366
+
367
+ def test_command_success_response_to_dict(self):
368
+ """Test CommandSuccessResponse serialization to dict."""
369
+ response = CommandSuccessResponse(
370
+ result={"data": "test"}
371
+ )
372
+
373
+ response_dict = response.model_dump()
374
+ assert response_dict["result"] == {"data": "test"}
375
+
376
+
377
+ class TestCommandErrorResponse:
378
+ """Test cases for CommandErrorResponse model."""
379
+
380
+ def test_command_error_response_creation(self):
381
+ """Test CommandErrorResponse creation."""
382
+ error = JsonRpcError(code=-32601, message="Method not found")
383
+ response = CommandErrorResponse(error=error)
384
+
385
+ assert response.error == error
386
+
387
+ def test_command_error_response_with_id(self):
388
+ """Test CommandErrorResponse creation with id."""
389
+ error = JsonRpcError(code=-32700, message="Parse error")
390
+ response = CommandErrorResponse(error=error)
391
+
392
+ assert response.error == error
393
+
394
+ def test_command_error_response_to_dict(self):
395
+ """Test CommandErrorResponse serialization to dict."""
396
+ error = JsonRpcError(code=-32602, message="Invalid params")
397
+ response = CommandErrorResponse(error=error)
398
+
399
+ response_dict = response.model_dump()
400
+ assert response_dict["error"]["code"] == -32602
401
+ assert response_dict["error"]["message"] == "Invalid params"
402
+
403
+
404
+ class TestAPIToolDescription:
405
+ """Test cases for APIToolDescription class."""
406
+
407
+ @pytest.fixture
408
+ def mock_registry(self):
409
+ """Create a mock command registry for testing."""
410
+ from unittest.mock import Mock
411
+
412
+ registry = Mock()
413
+ registry.get_all_metadata.return_value = {
414
+ "help": {
415
+ "summary": "Show help information",
416
+ "description": "Display help for commands",
417
+ "params": {
418
+ "command": {
419
+ "type": "строка",
420
+ "description": "Command name",
421
+ "required": False
422
+ }
423
+ },
424
+ "examples": [
425
+ {
426
+ "command": "help",
427
+ "params": {"command": "config"}
428
+ }
429
+ ]
430
+ },
431
+ "config": {
432
+ "summary": "Get configuration",
433
+ "description": "Retrieve configuration settings",
434
+ "params": {
435
+ "section": {
436
+ "type": "строка",
437
+ "description": "Configuration section",
438
+ "required": True
439
+ }
440
+ },
441
+ "examples": [
442
+ {
443
+ "command": "config",
444
+ "params": {"section": "database"}
445
+ }
446
+ ]
447
+ }
448
+ }
449
+
450
+ return registry
451
+
452
+ def test_generate_tool_description(self, mock_registry):
453
+ """Test tool description generation."""
454
+ tool_name = "test_tool"
455
+
456
+ description = APIToolDescription.generate_tool_description(tool_name, mock_registry)
457
+
458
+ assert description["name"] == tool_name
459
+ assert "description" in description
460
+ assert "supported_commands" in description
461
+ assert "examples" in description
462
+ assert "help" in description["supported_commands"]
463
+ assert "config" in description["supported_commands"]
464
+
465
+ def test_generate_tool_description_text(self, mock_registry):
466
+ """Test tool description text generation."""
467
+ tool_name = "test_tool"
468
+
469
+ text = APIToolDescription.generate_tool_description_text(tool_name, mock_registry)
470
+
471
+ assert isinstance(text, str)
472
+ assert tool_name in text
473
+ assert "help" in text
474
+ assert "config" in text
475
+
476
+ def test_simplify_type_conversion(self):
477
+ """Test type simplification for various input types."""
478
+ # Test string types
479
+ assert APIToolDescription._simplify_type("str") == "строка"
480
+ assert APIToolDescription._simplify_type("int") == "целое число"
481
+ assert APIToolDescription._simplify_type("float") == "число"
482
+ assert APIToolDescription._simplify_type("bool") == "логическое значение"
483
+ assert APIToolDescription._simplify_type("List") == "список"
484
+ assert APIToolDescription._simplify_type("Dict") == "объект"
485
+
486
+ # Test unknown type
487
+ assert APIToolDescription._simplify_type("неизвестный") == "значение"
488
+
489
+ def test_extract_param_description(self):
490
+ """Test parameter description extraction."""
491
+ doc_string = """
492
+ Test command.
493
+
494
+ Args:
495
+ param1: First parameter description
496
+ param2: Second parameter description
497
+ """
498
+
499
+ description = APIToolDescription._extract_param_description(doc_string, "param1")
500
+ assert "First parameter description" in description
501
+
502
+ description = APIToolDescription._extract_param_description(doc_string, "param2")
503
+ assert "Second parameter description" in description
504
+
505
+ # Test non-existent parameter
506
+ description = APIToolDescription._extract_param_description(doc_string, "nonexistent")
507
+ assert description == ""
508
+
509
+ def test_extract_param_description_no_args_section(self):
510
+ """Test parameter description extraction without Args section."""
511
+ doc_string = "Simple command without Args section."
512
+
513
+ description = APIToolDescription._extract_param_description(doc_string, "param")
514
+ assert description == ""
515
+
516
+ def test_extract_param_description_empty_docstring(self):
517
+ """Test parameter description extraction with empty docstring."""
518
+ description = APIToolDescription._extract_param_description("", "param")
519
+ assert description == ""
520
+
521
+ def test_generate_tool_description_empty_registry(self, mock_registry):
522
+ """Test tool description generation with empty registry."""
523
+ mock_registry.get_all_metadata.return_value = {}
524
+
525
+ description = APIToolDescription.generate_tool_description("empty_tool", mock_registry)
526
+
527
+ assert description["name"] == "empty_tool"
528
+ assert description["supported_commands"] == {}
529
+ assert description["examples"] == []
530
+
531
+ def test_generate_tool_description_with_missing_fields(self, mock_registry):
532
+ """Test tool description generation with missing metadata fields."""
533
+ mock_registry.get_all_metadata.return_value = {
534
+ "incomplete": {
535
+ "summary": "Incomplete command",
536
+ "description": "Incomplete command description",
537
+ "params": {}
538
+ # Missing examples
539
+ }
540
+ }
541
+
542
+ description = APIToolDescription.generate_tool_description("incomplete_tool", mock_registry)
543
+
544
+ assert "incomplete" in description["supported_commands"]
545
+ # Should handle missing fields gracefully
546
+ assert description["supported_commands"]["incomplete"]["summary"] == "Incomplete command"