mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.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 (253) hide show
  1. mcp_proxy_adapter/__main__.py +12 -0
  2. mcp_proxy_adapter/api/app.py +254 -33
  3. mcp_proxy_adapter/api/handlers.py +32 -6
  4. mcp_proxy_adapter/api/middleware/__init__.py +36 -30
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
  7. mcp_proxy_adapter/api/middleware/factory.py +243 -0
  8. mcp_proxy_adapter/api/middleware/logging.py +32 -6
  9. mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
  10. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  11. mcp_proxy_adapter/api/middleware/unified_security.py +152 -0
  12. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
  13. mcp_proxy_adapter/commands/__init__.py +19 -4
  14. mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
  15. mcp_proxy_adapter/commands/base.py +66 -32
  16. mcp_proxy_adapter/commands/builtin_commands.py +95 -0
  17. mcp_proxy_adapter/commands/catalog_manager.py +838 -0
  18. mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
  19. mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
  20. mcp_proxy_adapter/commands/command_registry.py +711 -354
  21. mcp_proxy_adapter/commands/dependency_manager.py +245 -0
  22. mcp_proxy_adapter/commands/echo_command.py +81 -0
  23. mcp_proxy_adapter/commands/health_command.py +7 -0
  24. mcp_proxy_adapter/commands/help_command.py +21 -14
  25. mcp_proxy_adapter/commands/hooks.py +200 -167
  26. mcp_proxy_adapter/commands/key_management_command.py +506 -0
  27. mcp_proxy_adapter/commands/load_command.py +176 -0
  28. mcp_proxy_adapter/commands/plugins_command.py +235 -0
  29. mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
  30. mcp_proxy_adapter/commands/proxy_registration_command.py +409 -0
  31. mcp_proxy_adapter/commands/reload_command.py +48 -50
  32. mcp_proxy_adapter/commands/result.py +1 -0
  33. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  34. mcp_proxy_adapter/commands/roles_management_command.py +697 -0
  35. mcp_proxy_adapter/commands/security_command.py +488 -0
  36. mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
  37. mcp_proxy_adapter/commands/token_management_command.py +529 -0
  38. mcp_proxy_adapter/commands/transport_management_command.py +144 -0
  39. mcp_proxy_adapter/commands/unload_command.py +158 -0
  40. mcp_proxy_adapter/config.py +159 -2
  41. mcp_proxy_adapter/core/app_factory.py +326 -0
  42. mcp_proxy_adapter/core/auth_validator.py +606 -0
  43. mcp_proxy_adapter/core/certificate_utils.py +827 -0
  44. mcp_proxy_adapter/core/client_security.py +384 -0
  45. mcp_proxy_adapter/core/config_converter.py +405 -0
  46. mcp_proxy_adapter/core/config_validator.py +218 -0
  47. mcp_proxy_adapter/core/logging.py +19 -3
  48. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  49. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  50. mcp_proxy_adapter/core/protocol_manager.py +235 -0
  51. mcp_proxy_adapter/core/proxy_client.py +602 -0
  52. mcp_proxy_adapter/core/proxy_registration.py +522 -0
  53. mcp_proxy_adapter/core/role_utils.py +426 -0
  54. mcp_proxy_adapter/core/security_adapter.py +370 -0
  55. mcp_proxy_adapter/core/security_factory.py +239 -0
  56. mcp_proxy_adapter/core/security_integration.py +277 -0
  57. mcp_proxy_adapter/core/server_adapter.py +345 -0
  58. mcp_proxy_adapter/core/server_engine.py +364 -0
  59. mcp_proxy_adapter/core/settings.py +1 -0
  60. mcp_proxy_adapter/core/ssl_utils.py +233 -0
  61. mcp_proxy_adapter/core/transport_manager.py +292 -0
  62. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  63. mcp_proxy_adapter/custom_openapi.py +22 -11
  64. mcp_proxy_adapter/examples/README.md +230 -97
  65. mcp_proxy_adapter/examples/README_EN.md +258 -0
  66. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  67. mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
  68. mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
  69. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  70. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  71. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +39 -0
  72. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +25 -0
  73. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  74. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  75. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  76. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  77. mcp_proxy_adapter/examples/cert_config.json +9 -0
  78. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  79. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  80. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  81. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  82. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  83. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  84. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  85. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  86. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  87. mcp_proxy_adapter/examples/certs/client.key +52 -0
  88. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  89. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  90. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  91. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  92. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  93. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  94. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  95. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  96. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  97. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  98. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  99. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  100. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  101. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  102. mcp_proxy_adapter/examples/certs/server.key +52 -0
  103. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  104. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  105. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  106. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  107. mcp_proxy_adapter/examples/certs/user.key +52 -0
  108. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  109. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  110. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  111. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  112. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  113. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  114. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  115. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  116. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  117. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  118. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  119. mcp_proxy_adapter/examples/demo_client.py +341 -0
  120. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  121. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  122. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  123. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  124. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  125. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  126. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  127. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  128. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  129. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  130. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  131. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  132. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  133. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  134. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  135. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  136. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  137. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  138. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  139. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  140. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  141. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  142. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  143. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  144. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  145. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  146. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  147. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  148. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  149. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  150. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  151. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  152. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  153. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  154. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  155. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  156. mcp_proxy_adapter/examples/roles.json +38 -0
  157. mcp_proxy_adapter/examples/run_example.py +81 -0
  158. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  159. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  160. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  161. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  162. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  163. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  164. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  165. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  166. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  167. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  168. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  169. mcp_proxy_adapter/examples/test_examples.py +344 -0
  170. mcp_proxy_adapter/examples/universal_client.py +628 -0
  171. mcp_proxy_adapter/main.py +186 -0
  172. mcp_proxy_adapter/utils/config_generator.py +639 -0
  173. mcp_proxy_adapter/version.py +2 -1
  174. mcp_proxy_adapter-6.1.0.dist-info/METADATA +205 -0
  175. mcp_proxy_adapter-6.1.0.dist-info/RECORD +193 -0
  176. mcp_proxy_adapter-6.1.0.dist-info/entry_points.txt +2 -0
  177. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/licenses/LICENSE +2 -2
  178. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  179. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  180. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  181. mcp_proxy_adapter/examples/__init__.py +0 -7
  182. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  183. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  184. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  185. mcp_proxy_adapter/examples/basic_server/config.json +0 -35
  186. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  187. mcp_proxy_adapter/examples/basic_server/server.py +0 -103
  188. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  189. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  190. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
  191. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  192. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  193. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  194. mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
  195. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  196. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  197. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  198. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  199. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  200. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  201. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  202. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  203. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  204. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  205. mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
  206. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  207. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  208. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  209. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  210. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  211. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  212. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  213. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  214. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  215. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  216. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  217. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  218. mcp_proxy_adapter/tests/__init__.py +0 -0
  219. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  220. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  221. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  222. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  223. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  224. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  225. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  226. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  227. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  228. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  229. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  230. mcp_proxy_adapter/tests/conftest.py +0 -131
  231. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  232. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  233. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  234. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  235. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  236. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  237. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  238. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  239. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  240. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  241. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  242. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  243. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  244. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  245. mcp_proxy_adapter/tests/test_config.py +0 -127
  246. mcp_proxy_adapter/tests/test_utils.py +0 -65
  247. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  248. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  249. mcp_proxy_adapter/tests/unit/test_config.py +0 -217
  250. mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
  251. mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
  252. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/WHEEL +0 -0
  253. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/top_level.txt +0 -0
@@ -1,522 +0,0 @@
1
- """
2
- Tests for API handlers module.
3
-
4
- This module contains comprehensive tests for the handlers module
5
- to ensure 90%+ code coverage.
6
- """
7
-
8
- import pytest
9
- import json
10
- from unittest.mock import Mock, patch, MagicMock, AsyncMock
11
- from typing import Dict, Any, List
12
-
13
- from fastapi import Request
14
-
15
- from mcp_proxy_adapter.api.handlers import (
16
- execute_command, handle_batch_json_rpc, handle_json_rpc,
17
- _create_error_response, get_server_health, get_commands_list
18
- )
19
- from mcp_proxy_adapter.core.errors import (
20
- NotFoundError, MethodNotFoundError, InvalidRequestError,
21
- InternalError, MicroserviceError, InvalidParamsError
22
- )
23
- from mcp_proxy_adapter.commands.command_registry import registry
24
-
25
-
26
- class TestExecuteCommand:
27
- """Test cases for execute_command function."""
28
-
29
- @pytest.fixture
30
- def mock_registry(self):
31
- """Create a mock registry for testing."""
32
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_reg:
33
- yield mock_reg
34
-
35
- @pytest.fixture
36
- def mock_command_class(self):
37
- """Create a mock command class for testing."""
38
- class MockCommand:
39
- name = "test_command"
40
-
41
- @classmethod
42
- def get_schema(cls):
43
- return {"type": "object", "properties": {}}
44
-
45
- @classmethod
46
- async def run(cls, **kwargs):
47
- return MockResult()
48
-
49
- async def execute(self, **kwargs):
50
- return {"result": "test"}
51
-
52
- class MockResult:
53
- def to_dict(self):
54
- return {"result": "test"}
55
-
56
- return MockCommand
57
-
58
- @pytest.mark.asyncio
59
- async def test_execute_command_success(self, mock_registry, mock_command_class):
60
- """Test successful command execution."""
61
- mock_registry.get_command.return_value = mock_command_class
62
-
63
- result = await execute_command("test_command", {"param": "value"})
64
-
65
- assert result == {"result": "test"}
66
- mock_registry.get_command.assert_called_once_with("test_command")
67
-
68
- @pytest.mark.asyncio
69
- async def test_execute_command_with_request_id(self, mock_registry, mock_command_class):
70
- """Test command execution with request ID."""
71
- mock_registry.get_command.return_value = mock_command_class
72
-
73
- result = await execute_command("test_command", {"param": "value"}, "req_123")
74
-
75
- assert result == {"result": "test"}
76
-
77
- @pytest.mark.asyncio
78
- async def test_execute_command_not_found(self, mock_registry):
79
- """Test command execution when command not found."""
80
- mock_registry.get_command.side_effect = NotFoundError("Command not found")
81
-
82
- with pytest.raises(MethodNotFoundError):
83
- await execute_command("nonexistent_command", {})
84
-
85
- @pytest.mark.asyncio
86
- async def test_execute_command_microservice_error(self, mock_registry):
87
- """Test command execution with microservice error."""
88
- mock_registry.get_command.side_effect = InvalidRequestError("Invalid parameters")
89
-
90
- with pytest.raises(InvalidRequestError):
91
- await execute_command("test_command", {})
92
-
93
- @pytest.mark.asyncio
94
- async def test_execute_command_general_exception(self, mock_registry):
95
- """Test command execution with general exception."""
96
- mock_registry.get_command.side_effect = Exception("Unexpected error")
97
-
98
- with pytest.raises(InternalError):
99
- await execute_command("test_command", {})
100
-
101
- @pytest.mark.asyncio
102
- async def test_execute_command_execution_time_logging(self, mock_registry, mock_command_class):
103
- """Test that execution time is logged."""
104
- mock_registry.get_command.return_value = mock_command_class
105
-
106
- with patch('mcp_proxy_adapter.api.handlers.logger') as mock_logger:
107
- await execute_command("test_command", {})
108
-
109
- # Check that timing was logged
110
- mock_logger.info.assert_called()
111
- call_args = mock_logger.info.call_args[0][0]
112
- assert "executed in" in call_args
113
-
114
-
115
- class TestHandleBatchJsonRpc:
116
- """Test cases for handle_batch_json_rpc function."""
117
-
118
- @pytest.fixture
119
- def mock_request(self):
120
- """Create a mock request object."""
121
- request = Mock(spec=Request)
122
- request.state.request_id = "batch_123"
123
- return request
124
-
125
- @pytest.mark.asyncio
126
- async def test_handle_batch_json_rpc_success(self, mock_request):
127
- """Test successful batch JSON-RPC handling."""
128
- batch_requests = [
129
- {"jsonrpc": "2.0", "method": "help", "id": 1},
130
- {"jsonrpc": "2.0", "method": "config", "id": 2}
131
- ]
132
-
133
- with patch('mcp_proxy_adapter.api.handlers.handle_json_rpc') as mock_handle:
134
- mock_handle.side_effect = [
135
- {"jsonrpc": "2.0", "result": "help_result", "id": 1},
136
- {"jsonrpc": "2.0", "result": "config_result", "id": 2}
137
- ]
138
-
139
- responses = await handle_batch_json_rpc(batch_requests, mock_request)
140
-
141
- assert len(responses) == 2
142
- assert responses[0]["id"] == 1
143
- assert responses[1]["id"] == 2
144
-
145
- @pytest.mark.asyncio
146
- async def test_handle_batch_json_rpc_without_request(self):
147
- """Test batch JSON-RPC handling without request object."""
148
- batch_requests = [
149
- {"jsonrpc": "2.0", "method": "help", "id": 1}
150
- ]
151
-
152
- with patch('mcp_proxy_adapter.api.handlers.handle_json_rpc') as mock_handle:
153
- mock_handle.return_value = {"jsonrpc": "2.0", "result": "help_result", "id": 1}
154
-
155
- responses = await handle_batch_json_rpc(batch_requests)
156
-
157
- assert len(responses) == 1
158
-
159
- @pytest.mark.asyncio
160
- async def test_handle_batch_json_rpc_empty_batch(self):
161
- """Test batch JSON-RPC handling with empty batch."""
162
- responses = await handle_batch_json_rpc([])
163
-
164
- assert responses == []
165
-
166
-
167
- class TestHandleJsonRpc:
168
- """Test cases for handle_json_rpc function."""
169
-
170
- @pytest.mark.asyncio
171
- async def test_handle_json_rpc_success(self):
172
- """Test successful JSON-RPC handling."""
173
- request_data = {
174
- "jsonrpc": "2.0",
175
- "method": "help",
176
- "id": 1
177
- }
178
-
179
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
180
- mock_execute.return_value = {"result": "help_result"}
181
-
182
- response = await handle_json_rpc(request_data)
183
-
184
- assert response["jsonrpc"] == "2.0"
185
- assert response["result"] == {"result": "help_result"}
186
- assert response["id"] == 1
187
-
188
- @pytest.mark.asyncio
189
- async def test_handle_json_rpc_with_request_id(self):
190
- """Test JSON-RPC handling with request ID."""
191
- request_data = {
192
- "jsonrpc": "2.0",
193
- "method": "help",
194
- "id": 1
195
- }
196
-
197
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
198
- mock_execute.return_value = {"result": "help_result"}
199
-
200
- response = await handle_json_rpc(request_data, "req_123")
201
-
202
- assert response["jsonrpc"] == "2.0"
203
- assert response["result"] == {"result": "help_result"}
204
-
205
- @pytest.mark.asyncio
206
- async def test_handle_json_rpc_invalid_version(self):
207
- """Test JSON-RPC handling with invalid version."""
208
- request_data = {
209
- "jsonrpc": "1.0", # Invalid version
210
- "method": "help",
211
- "id": 1
212
- }
213
-
214
- response = await handle_json_rpc(request_data)
215
-
216
- assert response["jsonrpc"] == "2.0"
217
- assert "error" in response
218
- assert response["error"]["code"] == -32600 # Invalid Request
219
-
220
- @pytest.mark.asyncio
221
- async def test_handle_json_rpc_missing_method(self):
222
- """Test JSON-RPC handling with missing method."""
223
- request_data = {
224
- "jsonrpc": "2.0",
225
- "id": 1
226
- # Missing method
227
- }
228
-
229
- response = await handle_json_rpc(request_data)
230
-
231
- assert response["jsonrpc"] == "2.0"
232
- assert "error" in response
233
- assert response["error"]["code"] == -32600 # Invalid Request
234
-
235
- @pytest.mark.asyncio
236
- async def test_handle_json_rpc_microservice_error(self):
237
- """Test JSON-RPC handling with microservice error."""
238
- request_data = {
239
- "jsonrpc": "2.0",
240
- "method": "help",
241
- "id": 1
242
- }
243
-
244
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
245
- mock_execute.side_effect = InvalidRequestError("Invalid parameters")
246
-
247
- response = await handle_json_rpc(request_data)
248
-
249
- assert response["jsonrpc"] == "2.0"
250
- assert "error" in response
251
- assert response["error"]["code"] == -32600 # Invalid Request
252
-
253
- @pytest.mark.asyncio
254
- async def test_handle_json_rpc_unhandled_exception(self):
255
- """Test JSON-RPC handling with unhandled exception."""
256
- request_data = {
257
- "jsonrpc": "2.0",
258
- "method": "help",
259
- "id": 1
260
- }
261
-
262
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
263
- mock_execute.side_effect = Exception("Unexpected error")
264
-
265
- response = await handle_json_rpc(request_data)
266
-
267
- assert response["jsonrpc"] == "2.0"
268
- assert "error" in response
269
- assert response["error"]["code"] == -32603 # Internal error
270
-
271
- @pytest.mark.asyncio
272
- async def test_handle_json_rpc_without_params(self):
273
- """Test JSON-RPC handling without parameters."""
274
- request_data = {
275
- "jsonrpc": "2.0",
276
- "method": "help",
277
- "id": 1
278
- # No params
279
- }
280
-
281
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
282
- mock_execute.return_value = {"result": "help_result"}
283
-
284
- response = await handle_json_rpc(request_data)
285
-
286
- assert response["jsonrpc"] == "2.0"
287
- assert response["result"] == {"result": "help_result"}
288
-
289
-
290
- class TestCreateErrorResponse:
291
- """Test cases for _create_error_response function."""
292
-
293
- def test_create_error_response(self):
294
- """Test creating error response."""
295
- error = InvalidRequestError("Test error")
296
- request_id = 123
297
-
298
- response = _create_error_response(error, request_id)
299
-
300
- assert response["jsonrpc"] == "2.0"
301
- assert response["error"]["code"] == -32600
302
- assert response["id"] == 123
303
-
304
- def test_create_error_response_with_none_id(self):
305
- """Test creating error response with None ID."""
306
- error = InvalidRequestError("Test error")
307
-
308
- response = _create_error_response(error, None)
309
-
310
- assert response["jsonrpc"] == "2.0"
311
- assert response["error"]["code"] == -32600
312
- assert response["id"] is None
313
-
314
-
315
- class TestGetServerHealth:
316
- """Test cases for get_server_health function."""
317
-
318
- @pytest.mark.asyncio
319
- async def test_get_server_health(self):
320
- """Test getting server health information."""
321
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
322
- mock_registry.get_all_commands.return_value = {"help": Mock(), "config": Mock()}
323
-
324
- health = await get_server_health()
325
-
326
- assert health["status"] == "ok"
327
- assert health["version"] == "1.0.0"
328
- assert "uptime" in health
329
- assert "components" in health
330
- assert health["components"]["commands"]["registered_count"] == 2
331
-
332
- @pytest.mark.asyncio
333
- async def test_get_server_health_with_empty_registry(self):
334
- """Test getting server health with empty registry."""
335
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
336
- mock_registry.get_all_commands.return_value = {}
337
-
338
- health = await get_server_health()
339
-
340
- assert health["status"] == "ok"
341
- assert health["components"]["commands"]["registered_count"] == 0
342
-
343
- @pytest.mark.asyncio
344
- async def test_get_server_health_system_info(self):
345
- """Test that system information is included."""
346
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
347
- mock_registry.get_all_commands.return_value = {}
348
-
349
- health = await get_server_health()
350
-
351
- assert "system" in health["components"]
352
- assert "process" in health["components"]
353
- assert "python_version" in health["components"]["system"]
354
- assert "platform" in health["components"]["system"]
355
- assert "cpu_count" in health["components"]["system"]
356
-
357
-
358
- class TestGetCommandsList:
359
- """Test cases for get_commands_list function."""
360
-
361
- @pytest.mark.asyncio
362
- async def test_get_commands_list(self):
363
- """Test getting commands list."""
364
- mock_command1 = Mock()
365
- mock_command1.get_schema.return_value = {
366
- "description": "Test command 1",
367
- "properties": {}
368
- }
369
-
370
- mock_command2 = Mock()
371
- mock_command2.get_schema.return_value = {
372
- "description": "Test command 2",
373
- "properties": {}
374
- }
375
-
376
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
377
- mock_registry.get_all_commands.return_value = {
378
- "help": mock_command1,
379
- "config": mock_command2
380
- }
381
-
382
- commands = await get_commands_list()
383
-
384
- assert len(commands) == 2
385
- assert "help" in commands
386
- assert "config" in commands
387
- assert commands["help"]["name"] == "help"
388
- assert commands["help"]["description"] == "Test command 1"
389
-
390
- @pytest.mark.asyncio
391
- async def test_get_commands_list_empty_registry(self):
392
- """Test getting commands list with empty registry."""
393
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
394
- mock_registry.get_all_commands.return_value = {}
395
-
396
- commands = await get_commands_list()
397
-
398
- assert commands == {}
399
-
400
- @pytest.mark.asyncio
401
- async def test_get_commands_list_command_without_description(self):
402
- """Test getting commands list with command without description."""
403
- mock_command = Mock()
404
- mock_command.get_schema.return_value = {
405
- "properties": {}
406
- # No description
407
- }
408
-
409
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
410
- mock_registry.get_all_commands.return_value = {"test": mock_command}
411
-
412
- commands = await get_commands_list()
413
-
414
- assert "test" in commands
415
- assert commands["test"]["description"] == ""
416
-
417
-
418
- class TestHandlersIntegration:
419
- """Integration tests for handlers."""
420
-
421
- @pytest.mark.asyncio
422
- async def test_full_json_rpc_workflow(self):
423
- """Test complete JSON-RPC workflow."""
424
- request_data = {
425
- "jsonrpc": "2.0",
426
- "method": "help",
427
- "id": 1
428
- }
429
-
430
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
431
- mock_execute.return_value = {"result": "help_result"}
432
-
433
- response = await handle_json_rpc(request_data)
434
-
435
- assert response["jsonrpc"] == "2.0"
436
- assert response["result"] == {"result": "help_result"}
437
- assert response["id"] == 1
438
-
439
- @pytest.mark.asyncio
440
- async def test_batch_json_rpc_workflow(self):
441
- """Test complete batch JSON-RPC workflow."""
442
- batch_requests = [
443
- {"jsonrpc": "2.0", "method": "help", "id": 1},
444
- {"jsonrpc": "2.0", "method": "config", "id": 2}
445
- ]
446
-
447
- with patch('mcp_proxy_adapter.api.handlers.handle_json_rpc') as mock_handle:
448
- mock_handle.side_effect = [
449
- {"jsonrpc": "2.0", "result": "help_result", "id": 1},
450
- {"jsonrpc": "2.0", "result": "config_result", "id": 2}
451
- ]
452
-
453
- responses = await handle_batch_json_rpc(batch_requests)
454
-
455
- assert len(responses) == 2
456
- assert responses[0]["id"] == 1
457
- assert responses[1]["id"] == 2
458
-
459
-
460
- class TestHandlersEdgeCases:
461
- """Test edge cases for handlers."""
462
-
463
- @pytest.mark.asyncio
464
- async def test_execute_command_with_complex_params(self):
465
- """Test command execution with complex parameters."""
466
- class MockCommandClass:
467
- @classmethod
468
- async def run(cls, **kwargs):
469
- return MockResult()
470
-
471
- class MockResult:
472
- def to_dict(self):
473
- return {"result": "complex"}
474
-
475
- with patch('mcp_proxy_adapter.api.handlers.registry') as mock_registry:
476
- mock_registry.get_command.return_value = MockCommandClass
477
-
478
- complex_params = {
479
- "nested": {"key": "value"},
480
- "list": [1, 2, 3],
481
- "boolean": True
482
- }
483
-
484
- result = await execute_command("test_command", complex_params)
485
-
486
- assert result == {"result": "complex"}
487
-
488
- @pytest.mark.asyncio
489
- async def test_json_rpc_with_null_params(self):
490
- """Test JSON-RPC handling with null parameters."""
491
- request_data = {
492
- "jsonrpc": "2.0",
493
- "method": "help",
494
- "params": None,
495
- "id": 1
496
- }
497
-
498
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
499
- mock_execute.return_value = {"result": "help_result"}
500
-
501
- response = await handle_json_rpc(request_data)
502
-
503
- assert response["jsonrpc"] == "2.0"
504
- assert response["result"] == {"result": "help_result"}
505
-
506
- @pytest.mark.asyncio
507
- async def test_json_rpc_without_id(self):
508
- """Test JSON-RPC handling without ID."""
509
- request_data = {
510
- "jsonrpc": "2.0",
511
- "method": "help"
512
- # No id
513
- }
514
-
515
- with patch('mcp_proxy_adapter.api.handlers.execute_command') as mock_execute:
516
- mock_execute.return_value = {"result": "help_result"}
517
-
518
- response = await handle_json_rpc(request_data)
519
-
520
- assert response["jsonrpc"] == "2.0"
521
- assert response["result"] == {"result": "help_result"}
522
- assert response["id"] is None