mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. mcp_proxy_adapter/__main__.py +27 -7
  2. mcp_proxy_adapter/api/app.py +209 -79
  3. mcp_proxy_adapter/api/handlers.py +16 -5
  4. mcp_proxy_adapter/api/middleware/__init__.py +14 -9
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/factory.py +36 -12
  7. mcp_proxy_adapter/api/middleware/protocol_middleware.py +84 -18
  8. mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
  9. mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -0
  10. mcp_proxy_adapter/commands/__init__.py +7 -1
  11. mcp_proxy_adapter/commands/base.py +7 -4
  12. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  13. mcp_proxy_adapter/commands/command_registry.py +8 -0
  14. mcp_proxy_adapter/commands/echo_command.py +81 -0
  15. mcp_proxy_adapter/commands/health_command.py +1 -1
  16. mcp_proxy_adapter/commands/help_command.py +21 -14
  17. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  18. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  19. mcp_proxy_adapter/commands/security_command.py +488 -0
  20. mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
  21. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  22. mcp_proxy_adapter/config.py +323 -40
  23. mcp_proxy_adapter/core/app_factory.py +410 -0
  24. mcp_proxy_adapter/core/app_runner.py +272 -0
  25. mcp_proxy_adapter/core/certificate_utils.py +291 -73
  26. mcp_proxy_adapter/core/client.py +574 -0
  27. mcp_proxy_adapter/core/client_manager.py +284 -0
  28. mcp_proxy_adapter/core/client_security.py +384 -0
  29. mcp_proxy_adapter/core/logging.py +8 -3
  30. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  31. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  32. mcp_proxy_adapter/core/protocol_manager.py +169 -10
  33. mcp_proxy_adapter/core/proxy_client.py +602 -0
  34. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  35. mcp_proxy_adapter/core/security_adapter.py +12 -15
  36. mcp_proxy_adapter/core/security_integration.py +286 -0
  37. mcp_proxy_adapter/core/server_adapter.py +282 -0
  38. mcp_proxy_adapter/core/server_engine.py +270 -0
  39. mcp_proxy_adapter/core/ssl_utils.py +13 -12
  40. mcp_proxy_adapter/core/transport_manager.py +5 -5
  41. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  42. mcp_proxy_adapter/examples/__init__.py +13 -4
  43. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  44. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  45. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  46. mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
  47. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  48. mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
  49. mcp_proxy_adapter/examples/debug_request_state.py +112 -0
  50. mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
  51. mcp_proxy_adapter/examples/demo_client.py +275 -0
  52. mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
  53. mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
  54. mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
  55. mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
  56. mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
  57. mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
  58. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
  59. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  60. mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
  61. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
  62. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  63. mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
  64. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
  65. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  66. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  67. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
  68. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  69. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  70. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
  71. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  72. mcp_proxy_adapter/examples/full_application/main.py +173 -0
  73. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  74. mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
  75. mcp_proxy_adapter/examples/generate_certificates.py +177 -0
  76. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  77. mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
  78. mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
  79. mcp_proxy_adapter/examples/run_example.py +59 -0
  80. mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
  81. mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
  82. mcp_proxy_adapter/examples/run_security_tests.py +544 -0
  83. mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
  84. mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
  85. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
  86. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
  87. mcp_proxy_adapter/examples/security_test_client.py +782 -0
  88. mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
  89. mcp_proxy_adapter/examples/test_config.py +148 -0
  90. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  91. mcp_proxy_adapter/examples/test_examples.py +281 -0
  92. mcp_proxy_adapter/examples/universal_client.py +620 -0
  93. mcp_proxy_adapter/main.py +66 -148
  94. mcp_proxy_adapter/utils/config_generator.py +1008 -0
  95. mcp_proxy_adapter/version.py +5 -2
  96. mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
  97. mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
  98. mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
  99. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
  100. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  101. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  102. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  103. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  104. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  105. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  106. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  107. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  108. mcp_proxy_adapter/api/middleware/security.py +0 -376
  109. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  110. mcp_proxy_adapter/examples/README.md +0 -124
  111. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  112. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  113. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  114. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  115. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  116. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  117. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  118. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  119. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  120. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  121. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  122. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  123. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  124. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  125. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  126. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  127. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  128. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  129. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  130. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  131. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  132. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  133. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  134. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  135. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  136. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  137. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  138. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  139. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  140. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  141. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  142. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  143. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  144. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  145. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  146. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  147. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  148. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  149. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  150. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  151. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  152. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  153. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  154. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  155. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  156. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  157. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  158. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  159. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  160. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  161. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  162. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  163. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  164. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  165. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  166. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  167. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  168. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  169. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  170. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  171. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  172. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  173. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  174. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  175. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  176. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  177. mcp_proxy_adapter/tests/__init__.py +0 -0
  178. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  179. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  180. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  181. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  182. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  183. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  184. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  185. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  186. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  187. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  188. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  189. mcp_proxy_adapter/tests/conftest.py +0 -131
  190. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  191. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  192. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  193. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  194. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  195. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  196. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  197. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  198. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  199. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  200. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  201. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  202. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  203. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  204. mcp_proxy_adapter/tests/test_config.py +0 -127
  205. mcp_proxy_adapter/tests/test_utils.py +0 -65
  206. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  207. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  208. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  209. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  210. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  211. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.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)