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,436 +0,0 @@
1
- """
2
- Tests for base command module.
3
-
4
- This module contains comprehensive tests for the base Command class
5
- to ensure 90%+ code coverage.
6
- """
7
-
8
- import pytest
9
- import inspect
10
- from unittest.mock import Mock, patch, MagicMock, AsyncMock
11
- from typing import Dict, Any
12
-
13
- from mcp_proxy_adapter.commands.base import Command
14
- from mcp_proxy_adapter.commands.result import SuccessResult, ErrorResult
15
- from mcp_proxy_adapter.core.errors import (
16
- ValidationError, InvalidParamsError, NotFoundError,
17
- TimeoutError, CommandError, InternalError
18
- )
19
-
20
-
21
- class MockResultClass:
22
- """Mock result class for testing."""
23
- def to_dict(self):
24
- return {"status": "success", "data": "test_data"}
25
-
26
- @classmethod
27
- def get_schema(cls):
28
- return {"type": "object", "properties": {"data": {"type": "string"}}}
29
-
30
-
31
- class TestCommand(Command):
32
- """Test command class for testing."""
33
- name = "test_command"
34
- result_class = MockResultClass
35
-
36
- async def execute(self, **kwargs):
37
- return SuccessResult(data=kwargs)
38
-
39
-
40
- def test_success_result():
41
- """Test success result creation."""
42
- result = SuccessResult(data="test_data")
43
- assert result.data == "test_data"
44
- result_dict = result.to_dict()
45
- assert result_dict["success"] is True
46
-
47
-
48
- def test_error_result():
49
- """Test error result creation."""
50
- result = ErrorResult(message="Test error", code=400)
51
- assert result.message == "Test error"
52
- assert result.code == 400
53
-
54
-
55
- class TestCommandClass:
56
- """Test cases for Command class."""
57
-
58
- @pytest.mark.asyncio
59
- async def test_execute(self):
60
- """Test execute method."""
61
- command = TestCommand()
62
- result = await command.execute(test_param="value")
63
- assert isinstance(result, SuccessResult)
64
-
65
- @pytest.mark.asyncio
66
- async def test_run(self):
67
- """Test run method (with validation)."""
68
- with patch('mcp_proxy_adapter.commands.base.registry', create=True) as mock_registry:
69
- mock_registry.get_priority_command.return_value = TestCommand
70
- mock_registry.has_instance.return_value = False
71
-
72
- # Mock the command execution to avoid registry lookup
73
- with patch.object(TestCommand, 'execute', return_value=SuccessResult(data={"value": "test_value"})):
74
- result = await TestCommand.run(value="test_value")
75
- assert isinstance(result, SuccessResult)
76
- assert result.data == {"value": "test_value"}
77
-
78
- def test_get_schema(self):
79
- """Test get_schema method."""
80
- schema = TestCommand.get_schema()
81
- assert schema["type"] == "object"
82
- assert "properties" in schema
83
-
84
- def test_get_result_schema(self):
85
- """Test get_result_schema method."""
86
- schema = TestCommand.get_result_schema()
87
- assert schema["type"] == "object"
88
- assert "properties" in schema
89
-
90
- def test_get_param_info(self):
91
- """Test get_param_info method."""
92
- param_info = TestCommand.get_param_info()
93
- assert isinstance(param_info, dict)
94
-
95
- def test_validate_params_none(self):
96
- """Test validate_params with None."""
97
- params = TestCommand.validate_params(None)
98
- assert params == {}
99
-
100
- def test_validate_params_empty_dict(self):
101
- """Test validate_params with empty dict."""
102
- params = TestCommand.validate_params({})
103
- assert params == {}
104
-
105
- def test_validate_params_with_none_values(self):
106
- """Test validate_params with None values."""
107
- params = TestCommand.validate_params({"param1": None, "param2": "value"})
108
- assert "param1" not in params
109
- assert params["param2"] == "value"
110
-
111
- def test_validate_params_with_empty_strings(self):
112
- """Test validate_params with empty strings."""
113
- params = TestCommand.validate_params({"param1": "", "param2": "null", "param3": "value"})
114
- assert "param1" not in params
115
- assert "param2" not in params
116
- assert params["param3"] == "value"
117
-
118
- def test_validate_params_with_cmdname_none(self):
119
- """Test validate_params with cmdname parameter."""
120
- params = TestCommand.validate_params({"cmdname": None, "other": "value"})
121
- assert params["cmdname"] is None
122
- assert params["other"] == "value"
123
-
124
- def test_validate_params_copy_input(self):
125
- """Test that validate_params doesn't modify input."""
126
- input_params = {"param1": "value1", "param2": None}
127
- result = TestCommand.validate_params(input_params)
128
- assert input_params == {"param1": "value1", "param2": None} # Input unchanged
129
- assert "param2" not in result # Result filtered
130
-
131
- @pytest.mark.asyncio
132
- async def test_run_with_hooks_skip_processing(self):
133
- """Test run method when hooks skip standard processing."""
134
- with patch('mcp_proxy_adapter.commands.base.hooks') as mock_hooks:
135
- mock_hooks.execute_before_hooks.return_value = Mock(standard_processing=False)
136
-
137
- result = await TestCommand.run(test_param="value")
138
- assert isinstance(result, SuccessResult)
139
- assert result.data == {"test_param": "value"}
140
-
141
- @pytest.mark.asyncio
142
- async def test_run_command_not_found(self):
143
- """Test run method when command not found."""
144
- with patch('mcp_proxy_adapter.commands.base.registry', create=True) as mock_registry:
145
- mock_registry.get_priority_command.return_value = None
146
-
147
- result = await TestCommand.run(test_param="value")
148
- assert isinstance(result, ErrorResult)
149
- assert "not found" in result.message
150
-
151
- @pytest.mark.asyncio
152
- async def test_run_with_registry_instance(self):
153
- """Test run method with existing registry instance."""
154
- mock_command = Mock()
155
- mock_command.execute = AsyncMock(return_value=SuccessResult(data="test"))
156
-
157
- with patch('mcp_proxy_adapter.commands.base.registry', create=True) as mock_registry:
158
- mock_registry.get_priority_command.return_value = TestCommand
159
- mock_registry.has_instance.return_value = True
160
- mock_registry.get_command_instance.return_value = mock_command
161
-
162
- result = await TestCommand.run(test_param="value")
163
- assert isinstance(result, SuccessResult)
164
-
165
- @pytest.mark.asyncio
166
- async def test_run_validation_error(self):
167
- """Test run method with validation error."""
168
- with patch('mcp_proxy_adapter.commands.base.registry', create=True) as mock_registry:
169
- mock_registry.get_priority_command.return_value = TestCommand
170
- mock_registry.has_instance.return_value = False
171
-
172
- # Mock TestCommand to raise ValidationError
173
- with patch.object(TestCommand, 'execute', side_effect=ValidationError("Invalid params")):
174
- result = await TestCommand.run(test_param="value")
175
- assert isinstance(result, ErrorResult)
176
- assert "Invalid params" in result.message
177
-
178
- @pytest.mark.asyncio
179
- async def test_run_invalid_params_error(self):
180
- """Test run method with invalid params error."""
181
- with patch('mcp_proxy_adapter.commands.base.registry', create=True) as mock_registry:
182
- mock_registry.get_priority_command.return_value = TestCommand
183
- mock_registry.has_instance.return_value = False
184
-
185
- with patch.object(TestCommand, 'execute', side_effect=InvalidParamsError("Invalid params")):
186
- result = await TestCommand.run(test_param="value")
187
- assert isinstance(result, ErrorResult)
188
- assert "Invalid params" in result.message
189
-
190
- @pytest.mark.asyncio
191
- async def test_run_not_found_error(self):
192
- """Test run method with not found error."""
193
- with patch('mcp_proxy_adapter.commands.base.registry', create=True) as mock_registry:
194
- mock_registry.get_priority_command.return_value = TestCommand
195
- mock_registry.has_instance.return_value = False
196
-
197
- with patch.object(TestCommand, 'execute', side_effect=NotFoundError("Not found")):
198
- result = await TestCommand.run(test_param="value")
199
- assert isinstance(result, ErrorResult)
200
- assert "Not found" in result.message
201
-
202
- @pytest.mark.asyncio
203
- async def test_run_timeout_error(self):
204
- """Test run method with timeout error."""
205
- with patch('mcp_proxy_adapter.commands.base.registry', create=True) as mock_registry:
206
- mock_registry.get_priority_command.return_value = TestCommand
207
- mock_registry.has_instance.return_value = False
208
-
209
- with patch.object(TestCommand, 'execute', side_effect=TimeoutError("Timeout")):
210
- result = await TestCommand.run(test_param="value")
211
- assert isinstance(result, ErrorResult)
212
- assert "Timeout" in result.message
213
-
214
- @pytest.mark.asyncio
215
- async def test_run_command_error(self):
216
- """Test run method with command error."""
217
- with patch('mcp_proxy_adapter.commands.base.registry', create=True) as mock_registry:
218
- mock_registry.get_priority_command.return_value = TestCommand
219
- mock_registry.has_instance.return_value = False
220
-
221
- with patch.object(TestCommand, 'execute', side_effect=CommandError("Command error")):
222
- result = await TestCommand.run(test_param="value")
223
- assert isinstance(result, ErrorResult)
224
- assert "Command error" in result.message
225
-
226
- @pytest.mark.asyncio
227
- async def test_run_unexpected_exception(self):
228
- """Test run method with unexpected exception."""
229
- with patch('mcp_proxy_adapter.commands.base.registry', create=True) as mock_registry:
230
- mock_registry.get_priority_command.return_value = TestCommand
231
- mock_registry.has_instance.return_value = False
232
-
233
- with patch.object(TestCommand, 'execute', side_effect=Exception("Unexpected error")):
234
- result = await TestCommand.run(test_param="value")
235
- assert isinstance(result, ErrorResult)
236
- assert "Command execution error" in result.message
237
-
238
- @pytest.mark.asyncio
239
- async def test_run_with_none_kwargs(self):
240
- """Test run method with None kwargs."""
241
- with patch('mcp_proxy_adapter.commands.base.registry', create=True) as mock_registry:
242
- mock_registry.get_priority_command.return_value = TestCommand
243
- mock_registry.has_instance.return_value = False
244
-
245
- result = await TestCommand.run()
246
- assert isinstance(result, SuccessResult)
247
-
248
- def test_get_metadata(self):
249
- """Test get_metadata method."""
250
- metadata = TestCommand.get_metadata()
251
- assert isinstance(metadata, dict)
252
- assert "name" in metadata
253
- assert "summary" in metadata
254
- assert "params" in metadata
255
-
256
- def test_get_metadata_with_schema(self):
257
- """Test get_metadata method with schema."""
258
- with patch.object(TestCommand, 'get_param_info') as mock_param_info:
259
- mock_param_info.return_value = {
260
- "param1": {"type": "string", "description": "Test param"}
261
- }
262
-
263
- metadata = TestCommand.get_metadata()
264
- assert "params" in metadata
265
- assert "param1" in metadata["params"]
266
-
267
- def test_generate_examples(self):
268
- """Test _generate_examples method."""
269
- params = {
270
- "param1": {"type": "string", "description": "Test param"}
271
- }
272
-
273
- examples = TestCommand._generate_examples(params)
274
- assert isinstance(examples, list)
275
- assert len(examples) > 0
276
-
277
- def test_generate_examples_empty_params(self):
278
- """Test _generate_examples with empty params."""
279
- examples = TestCommand._generate_examples({})
280
- assert isinstance(examples, list)
281
-
282
- def test_get_param_info_with_annotations(self):
283
- """Test get_param_info with type annotations."""
284
- class AnnotatedCommand(Command):
285
- name = "annotated_command"
286
- result_class = MockResultClass
287
-
288
- async def execute(self, param1: str, param2: int = 42):
289
- return SuccessResult(data={"param1": param1, "param2": param2})
290
-
291
- param_info = AnnotatedCommand.get_param_info()
292
- assert "param1" in param_info
293
- assert "param2" in param_info
294
- assert param_info["param1"]["required"] is True
295
- assert param_info["param2"]["required"] is False
296
-
297
- def test_get_param_info_without_annotations(self):
298
- """Test get_param_info without type annotations."""
299
- class NoAnnotationCommand(Command):
300
- name = "no_annotation_command"
301
- result_class = MockResultClass
302
-
303
- async def execute(self, param1, param2=42):
304
- return SuccessResult(data={"param1": param1, "param2": param2})
305
-
306
- param_info = NoAnnotationCommand.get_param_info()
307
- assert "param1" in param_info
308
- assert "param2" in param_info
309
- assert param_info["param1"]["required"] is True
310
- assert param_info["param2"]["required"] is False
311
-
312
- def test_get_result_schema_with_result_class(self):
313
- """Test get_result_schema with result class."""
314
- class MockResultClassWithSchema:
315
- @classmethod
316
- def get_schema(cls):
317
- return {"type": "object", "properties": {"data": {"type": "string"}}}
318
-
319
- class CommandWithResultClass(Command):
320
- name = "command_with_result"
321
- result_class = MockResultClassWithSchema
322
-
323
- async def execute(self, **kwargs):
324
- return SuccessResult(data="test")
325
-
326
- schema = CommandWithResultClass.get_result_schema()
327
- assert schema["type"] == "object"
328
- assert "properties" in schema
329
-
330
- def test_get_result_schema_without_result_class(self):
331
- """Test get_result_schema without result class."""
332
- class CommandWithoutResultClass(Command):
333
- name = "command_without_result"
334
-
335
- async def execute(self, **kwargs):
336
- return SuccessResult(data="test")
337
-
338
- schema = CommandWithoutResultClass.get_result_schema()
339
- assert schema == {}
340
-
341
- def test_command_name_generation(self):
342
- """Test command name generation from class name."""
343
- class TestCommandName(Command):
344
- result_class = MockResultClass
345
-
346
- async def execute(self, **kwargs):
347
- return SuccessResult(data="test")
348
-
349
- # Test that name is generated from class name
350
- # The name will be generated dynamically in the run method
351
- # So we test that the class can be instantiated
352
- command = TestCommandName()
353
- assert command is not None
354
-
355
- def test_command_name_override(self):
356
- """Test command name override."""
357
- class CustomNamedCommand(Command):
358
- name = "custom_name"
359
- result_class = MockResultClass
360
-
361
- async def execute(self, **kwargs):
362
- return SuccessResult(data="test")
363
-
364
- assert CustomNamedCommand.name == "custom_name"
365
-
366
-
367
- class TestCommandEdgeCases:
368
- """Test edge cases for Command class."""
369
-
370
- def test_validate_params_with_various_none_values(self):
371
- """Test validate_params with various None-like values."""
372
- params = {
373
- "null_str": "null",
374
- "none_str": "none",
375
- "empty_str": "",
376
- "real_none": None,
377
- "valid_value": "test"
378
- }
379
-
380
- result = TestCommand.validate_params(params)
381
- assert "null_str" not in result
382
- assert "none_str" not in result
383
- assert "empty_str" not in result
384
- assert "real_none" not in result
385
- assert result["valid_value"] == "test"
386
-
387
- def test_validate_params_case_insensitive(self):
388
- """Test validate_params case insensitive handling."""
389
- params = {
390
- "null_upper": "NULL",
391
- "none_upper": "NONE",
392
- "valid_value": "test"
393
- }
394
-
395
- result = TestCommand.validate_params(params)
396
- assert "null_upper" not in result
397
- assert "none_upper" not in result
398
- assert result["valid_value"] == "test"
399
-
400
- @pytest.mark.asyncio
401
- async def test_run_with_complex_hook_context(self):
402
- """Test run method with complex hook context."""
403
- mock_hook_context = Mock()
404
- mock_hook_context.standard_processing = False
405
-
406
- with patch('mcp_proxy_adapter.commands.base.hooks') as mock_hooks:
407
- mock_hooks.execute_before_hooks.return_value = mock_hook_context
408
-
409
- result = await TestCommand.run(complex_param={"nested": "value"})
410
- assert isinstance(result, SuccessResult)
411
-
412
- @pytest.mark.asyncio
413
- async def test_run_with_after_hooks(self):
414
- """Test run method with after hooks execution."""
415
- with patch('mcp_proxy_adapter.commands.base.registry', create=True) as mock_registry:
416
- mock_registry.get_priority_command.return_value = TestCommand
417
- mock_registry.has_instance.return_value = False
418
-
419
- with patch('mcp_proxy_adapter.commands.base.hooks') as mock_hooks:
420
- mock_hooks.execute_before_hooks.return_value = Mock(standard_processing=True)
421
-
422
- result = await TestCommand.run(test_param="value")
423
-
424
- # Verify after hooks were called
425
- mock_hooks.execute_after_hooks.assert_called_once()
426
-
427
- def test_get_metadata_with_examples(self):
428
- """Test get_metadata method with examples generation."""
429
- with patch.object(TestCommand, 'get_param_info') as mock_param_info:
430
- mock_param_info.return_value = {
431
- "param1": {"type": "string", "description": "Test param"}
432
- }
433
-
434
- metadata = TestCommand.get_metadata()
435
- assert "examples" in metadata
436
- assert isinstance(metadata["examples"], list)
@@ -1,217 +0,0 @@
1
- """
2
- Unit tests for configuration module.
3
- """
4
-
5
- import json
6
- import os
7
- import tempfile
8
- from typing import Generator
9
-
10
- import pytest
11
-
12
- from mcp_proxy_adapter.config import Config
13
-
14
-
15
- @pytest.fixture
16
- def temp_config_file() -> Generator[str, None, None]:
17
- """
18
- Creates temporary configuration file for tests.
19
-
20
- Returns:
21
- Path to temporary configuration file.
22
- """
23
- # Create temporary file
24
- fd, path = tempfile.mkstemp(suffix=".json")
25
-
26
- # Write test configuration
27
- test_config = {
28
- "server": {
29
- "host": "127.0.0.1",
30
- "port": 8000
31
- },
32
- "logging": {
33
- "level": "DEBUG",
34
- "file": "test.log"
35
- },
36
- "test_section": {
37
- "test_key": "test_value",
38
- "nested": {
39
- "key1": "value1",
40
- "key2": 42
41
- }
42
- }
43
- }
44
-
45
- with os.fdopen(fd, "w") as f:
46
- json.dump(test_config, f)
47
-
48
- yield path
49
-
50
- # Remove temporary file after tests
51
- os.unlink(path)
52
-
53
-
54
- @pytest.mark.unit
55
- def test_config_load_from_file(temp_config_file: str):
56
- """
57
- Test loading configuration from file.
58
-
59
- Args:
60
- temp_config_file: Path to temporary configuration file.
61
- """
62
- config = Config(temp_config_file)
63
-
64
- # Check loaded values
65
- assert config.get("server.host") == "127.0.0.1"
66
- assert config.get("server.port") == 8000
67
- assert config.get("logging.level") == "DEBUG"
68
- assert config.get("logging.file") == "test.log"
69
- assert config.get("test_section.test_key") == "test_value"
70
-
71
-
72
- @pytest.mark.unit
73
- def test_config_get_nested_values(temp_config_file: str):
74
- """
75
- Test getting nested values from configuration.
76
-
77
- Args:
78
- temp_config_file: Path to temporary configuration file.
79
- """
80
- config = Config(temp_config_file)
81
-
82
- # Get nested values
83
- assert config.get("test_section.nested.key1") == "value1"
84
- assert config.get("test_section.nested.key2") == 42
85
-
86
-
87
- @pytest.mark.unit
88
- def test_config_get_with_default(temp_config_file: str):
89
- """
90
- Test getting configuration values with default.
91
-
92
- Args:
93
- temp_config_file: Path to temporary configuration file.
94
- """
95
- config = Config(temp_config_file)
96
-
97
- # Get non-existent values with defaults
98
- assert config.get("non_existent", default="default") == "default"
99
- assert config.get("server.non_existent", default=123) == 123
100
- assert config.get("test_section.nested.non_existent", default=False) is False
101
-
102
-
103
- @pytest.mark.unit
104
- def test_config_get_without_default(temp_config_file: str):
105
- """
106
- Test getting non-existent configuration values without default.
107
-
108
- Args:
109
- temp_config_file: Path to temporary configuration file.
110
- """
111
- config = Config(temp_config_file)
112
-
113
- # Get non-existent values without defaults
114
- assert config.get("non_existent") is None
115
- assert config.get("server.non_existent") is None
116
- assert config.get("test_section.nested.non_existent") is None
117
-
118
-
119
- @pytest.mark.unit
120
- def test_config_set_value(temp_config_file: str):
121
- """
122
- Test setting configuration values.
123
-
124
- Args:
125
- temp_config_file: Path to temporary configuration file.
126
- """
127
- config = Config(temp_config_file)
128
-
129
- # Set values
130
- config.set("server.host", "localhost")
131
- config.set("logging.level", "INFO")
132
- config.set("new_section.new_key", "new_value")
133
-
134
- # Check set values
135
- assert config.get("server.host") == "localhost"
136
- assert config.get("logging.level") == "INFO"
137
- assert config.get("new_section.new_key") == "new_value"
138
-
139
-
140
- @pytest.mark.unit
141
- def test_config_save_and_load(temp_config_file: str):
142
- """
143
- Test saving and loading configuration.
144
-
145
- Args:
146
- temp_config_file: Path to temporary configuration file.
147
- """
148
- # Create and modify configuration
149
- config1 = Config(temp_config_file)
150
- config1.set("server.host", "localhost")
151
- config1.set("new_section.new_key", "new_value")
152
-
153
- # Save configuration
154
- config1.save()
155
-
156
- # Load configuration again
157
- config2 = Config(temp_config_file)
158
-
159
- # Check values
160
- assert config2.get("server.host") == "localhost"
161
- assert config2.get("new_section.new_key") == "new_value"
162
-
163
-
164
- @pytest.mark.unit
165
- def test_config_load_updated_file(temp_config_file: str):
166
- """
167
- Test loading updated configuration from file.
168
-
169
- Args:
170
- temp_config_file: Path to temporary configuration file.
171
- """
172
- # Load configuration
173
- config = Config(temp_config_file)
174
- original_host = config.get("server.host")
175
-
176
- # Modify file directly
177
- with open(temp_config_file, "r+") as f:
178
- data = json.load(f)
179
- data["server"]["host"] = "new_host"
180
- f.seek(0)
181
- f.truncate()
182
- json.dump(data, f)
183
-
184
- # Create new config instance to load updated file
185
- updated_config = Config(temp_config_file)
186
-
187
- # Check that value was updated
188
- assert updated_config.get("server.host") == "new_host"
189
- assert updated_config.get("server.host") != original_host
190
-
191
-
192
- @pytest.mark.unit
193
- def test_config_access_nested_sections(temp_config_file: str):
194
- """
195
- Test accessing nested configuration sections directly.
196
-
197
- Args:
198
- temp_config_file: Path to temporary configuration file.
199
- """
200
- config = Config(temp_config_file)
201
-
202
- # Get parent key then access nested keys
203
- server = config.get("server")
204
- logging = config.get("logging")
205
- test_section = config.get("test_section")
206
-
207
- # Check sections
208
- assert isinstance(server, dict)
209
- assert isinstance(logging, dict)
210
- assert isinstance(test_section, dict)
211
-
212
- assert server["host"] == "127.0.0.1"
213
- assert server["port"] == 8000
214
- assert logging["level"] == "DEBUG"
215
- assert logging["file"] == "test.log"
216
- assert test_section["test_key"] == "test_value"
217
- assert test_section["nested"]["key1"] == "value1"