mcp-proxy-adapter 3.0.0__py3-none-any.whl → 3.0.2__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.
- examples/basic_example/README.md +123 -9
- examples/basic_example/config.json +4 -0
- examples/basic_example/docs/EN/README.md +46 -5
- examples/basic_example/docs/RU/README.md +46 -5
- examples/basic_example/server.py +127 -21
- examples/complete_example/commands/system_command.py +1 -0
- examples/complete_example/server.py +68 -40
- examples/minimal_example/README.md +20 -6
- examples/minimal_example/config.json +7 -14
- examples/minimal_example/main.py +109 -40
- examples/minimal_example/simple_server.py +53 -14
- examples/minimal_example/tests/conftest.py +1 -1
- examples/minimal_example/tests/test_integration.py +8 -10
- examples/simple_server.py +12 -21
- examples/test_server.py +22 -14
- examples/tool_description_example.py +82 -0
- mcp_proxy_adapter/api/__init__.py +0 -0
- mcp_proxy_adapter/api/app.py +391 -0
- mcp_proxy_adapter/api/handlers.py +229 -0
- mcp_proxy_adapter/api/middleware/__init__.py +49 -0
- mcp_proxy_adapter/api/middleware/auth.py +146 -0
- mcp_proxy_adapter/api/middleware/base.py +79 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +198 -0
- mcp_proxy_adapter/api/middleware/logging.py +96 -0
- mcp_proxy_adapter/api/middleware/performance.py +83 -0
- mcp_proxy_adapter/api/middleware/rate_limit.py +152 -0
- mcp_proxy_adapter/api/schemas.py +305 -0
- mcp_proxy_adapter/api/tool_integration.py +223 -0
- mcp_proxy_adapter/api/tools.py +198 -0
- mcp_proxy_adapter/commands/__init__.py +19 -0
- mcp_proxy_adapter/commands/base.py +301 -0
- mcp_proxy_adapter/commands/command_registry.py +231 -0
- mcp_proxy_adapter/commands/config_command.py +113 -0
- mcp_proxy_adapter/commands/health_command.py +136 -0
- mcp_proxy_adapter/commands/help_command.py +193 -0
- mcp_proxy_adapter/commands/result.py +215 -0
- mcp_proxy_adapter/config.py +9 -0
- mcp_proxy_adapter/core/__init__.py +0 -0
- mcp_proxy_adapter/core/errors.py +173 -0
- mcp_proxy_adapter/core/logging.py +205 -0
- mcp_proxy_adapter/core/utils.py +138 -0
- mcp_proxy_adapter/custom_openapi.py +47 -10
- mcp_proxy_adapter/py.typed +0 -0
- mcp_proxy_adapter/schemas/base_schema.json +114 -0
- mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
- mcp_proxy_adapter/tests/__init__.py +0 -0
- mcp_proxy_adapter/tests/api/__init__.py +3 -0
- mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +115 -0
- mcp_proxy_adapter/tests/api/test_middleware.py +336 -0
- mcp_proxy_adapter/tests/commands/__init__.py +3 -0
- mcp_proxy_adapter/tests/commands/test_config_command.py +211 -0
- mcp_proxy_adapter/tests/commands/test_echo_command.py +127 -0
- mcp_proxy_adapter/tests/commands/test_help_command.py +133 -0
- mcp_proxy_adapter/tests/conftest.py +131 -0
- mcp_proxy_adapter/tests/functional/__init__.py +3 -0
- mcp_proxy_adapter/tests/functional/test_api.py +253 -0
- mcp_proxy_adapter/tests/integration/__init__.py +3 -0
- mcp_proxy_adapter/tests/integration/test_cmd_integration.py +130 -0
- mcp_proxy_adapter/tests/integration/test_integration.py +255 -0
- mcp_proxy_adapter/tests/performance/__init__.py +3 -0
- mcp_proxy_adapter/tests/performance/test_performance.py +189 -0
- mcp_proxy_adapter/tests/stubs/__init__.py +10 -0
- mcp_proxy_adapter/tests/stubs/echo_command.py +104 -0
- mcp_proxy_adapter/tests/test_api_endpoints.py +271 -0
- mcp_proxy_adapter/tests/test_api_handlers.py +289 -0
- mcp_proxy_adapter/tests/test_base_command.py +123 -0
- mcp_proxy_adapter/tests/test_batch_requests.py +117 -0
- mcp_proxy_adapter/tests/test_command_registry.py +245 -0
- mcp_proxy_adapter/tests/test_config.py +127 -0
- mcp_proxy_adapter/tests/test_utils.py +65 -0
- mcp_proxy_adapter/tests/unit/__init__.py +3 -0
- mcp_proxy_adapter/tests/unit/test_base_command.py +130 -0
- mcp_proxy_adapter/tests/unit/test_config.py +217 -0
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.2.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-3.0.2.dist-info/RECORD +109 -0
- examples/basic_example/config.yaml +0 -20
- examples/basic_example/main.py +0 -50
- examples/complete_example/main.py +0 -67
- examples/minimal_example/config.yaml +0 -26
- mcp_proxy_adapter/framework.py +0 -109
- mcp_proxy_adapter-3.0.0.dist-info/RECORD +0 -58
- {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.2.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.2.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
"""
|
2
|
+
Tests for the echo command.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
import asyncio
|
7
|
+
from typing import Dict, Any
|
8
|
+
import json
|
9
|
+
|
10
|
+
from mcp_proxy_adapter.tests.stubs.echo_command import EchoCommand
|
11
|
+
from mcp_proxy_adapter.tests.stubs.echo_command import EchoResult
|
12
|
+
|
13
|
+
|
14
|
+
@pytest.mark.unit
|
15
|
+
def test_echo_command_execution():
|
16
|
+
"""
|
17
|
+
Test execution of echo command.
|
18
|
+
"""
|
19
|
+
# Create test parameters
|
20
|
+
test_params = {
|
21
|
+
"string_param": "test_value",
|
22
|
+
"int_param": 42,
|
23
|
+
"bool_param": True,
|
24
|
+
"complex_param": {"nested": "value", "array": [1, 2, 3]}
|
25
|
+
}
|
26
|
+
|
27
|
+
# Create and execute command
|
28
|
+
command = EchoCommand()
|
29
|
+
result = asyncio.run(command.execute(**test_params))
|
30
|
+
|
31
|
+
# Check result type
|
32
|
+
assert isinstance(result, EchoResult)
|
33
|
+
|
34
|
+
# Check result content
|
35
|
+
assert result.params == test_params
|
36
|
+
assert result.params["string_param"] == "test_value"
|
37
|
+
assert result.params["int_param"] == 42
|
38
|
+
assert result.params["bool_param"] is True
|
39
|
+
assert result.params["complex_param"]["nested"] == "value"
|
40
|
+
assert result.params["complex_param"]["array"] == [1, 2, 3]
|
41
|
+
|
42
|
+
|
43
|
+
@pytest.mark.unit
|
44
|
+
def test_echo_result_serialization():
|
45
|
+
"""
|
46
|
+
Test serialization of echo result.
|
47
|
+
"""
|
48
|
+
# Create test parameters
|
49
|
+
test_params = {
|
50
|
+
"string_param": "test_value",
|
51
|
+
"int_param": 42,
|
52
|
+
"bool_param": True,
|
53
|
+
"complex_param": {"nested": "value", "array": [1, 2, 3]}
|
54
|
+
}
|
55
|
+
|
56
|
+
# Create result
|
57
|
+
result = EchoResult(params=test_params)
|
58
|
+
|
59
|
+
# Test to_dict method
|
60
|
+
result_dict = result.to_dict()
|
61
|
+
assert isinstance(result_dict, dict)
|
62
|
+
assert "params" in result_dict
|
63
|
+
assert result_dict["params"] == test_params
|
64
|
+
|
65
|
+
# Test that result can be properly serialized to JSON
|
66
|
+
json_str = json.dumps(result_dict)
|
67
|
+
parsed_json = json.loads(json_str)
|
68
|
+
assert parsed_json == result_dict
|
69
|
+
|
70
|
+
|
71
|
+
@pytest.mark.unit
|
72
|
+
def test_echo_command_schema():
|
73
|
+
"""
|
74
|
+
Test command schema generation.
|
75
|
+
"""
|
76
|
+
# Get schema
|
77
|
+
schema = EchoCommand.get_schema()
|
78
|
+
|
79
|
+
# Check schema structure
|
80
|
+
assert isinstance(schema, dict)
|
81
|
+
assert "type" in schema and schema["type"] == "object"
|
82
|
+
assert "additionalProperties" in schema and schema["additionalProperties"] is True
|
83
|
+
assert "description" in schema
|
84
|
+
|
85
|
+
|
86
|
+
@pytest.mark.unit
|
87
|
+
def test_echo_result_schema():
|
88
|
+
"""
|
89
|
+
Test result schema generation.
|
90
|
+
"""
|
91
|
+
# Get schema
|
92
|
+
schema = EchoResult.get_schema()
|
93
|
+
|
94
|
+
# Check schema structure
|
95
|
+
assert isinstance(schema, dict)
|
96
|
+
assert "type" in schema and schema["type"] == "object"
|
97
|
+
assert "properties" in schema
|
98
|
+
assert "params" in schema["properties"]
|
99
|
+
assert "required" in schema and "params" in schema["required"]
|
100
|
+
assert schema["properties"]["params"]["type"] == "object"
|
101
|
+
assert schema["properties"]["params"]["additionalProperties"] is True
|
102
|
+
|
103
|
+
|
104
|
+
@pytest.mark.unit
|
105
|
+
def test_echo_result_from_dict():
|
106
|
+
"""
|
107
|
+
Test creating result from dictionary.
|
108
|
+
"""
|
109
|
+
# Create test data
|
110
|
+
test_data = {
|
111
|
+
"params": {
|
112
|
+
"key1": "value1",
|
113
|
+
"key2": 42
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
# Create result from dict
|
118
|
+
result = EchoResult.from_dict(test_data)
|
119
|
+
|
120
|
+
# Check result
|
121
|
+
assert isinstance(result, EchoResult)
|
122
|
+
assert result.params == test_data["params"]
|
123
|
+
|
124
|
+
# Test with empty params
|
125
|
+
empty_result = EchoResult.from_dict({})
|
126
|
+
assert isinstance(empty_result, EchoResult)
|
127
|
+
assert empty_result.params == {}
|
@@ -0,0 +1,133 @@
|
|
1
|
+
"""
|
2
|
+
Tests for the help command.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
from unittest.mock import patch, MagicMock
|
7
|
+
|
8
|
+
from mcp_proxy_adapter.commands.help_command import HelpCommand, HelpResult
|
9
|
+
from mcp_proxy_adapter.core.errors import NotFoundError
|
10
|
+
|
11
|
+
|
12
|
+
@pytest.fixture
|
13
|
+
def mock_registry():
|
14
|
+
"""Mock for command registry."""
|
15
|
+
with patch("mcp_proxy_adapter.commands.help_command.registry") as mock_reg:
|
16
|
+
yield mock_reg
|
17
|
+
|
18
|
+
|
19
|
+
async def test_help_command_without_params(mock_registry):
|
20
|
+
"""Test help command without parameters."""
|
21
|
+
# Setup mocks
|
22
|
+
mock_registry.get_all_metadata.return_value = {
|
23
|
+
"help": {
|
24
|
+
"name": "help",
|
25
|
+
"summary": "Get help information",
|
26
|
+
"description": "Get help information",
|
27
|
+
"params": {},
|
28
|
+
"examples": []
|
29
|
+
},
|
30
|
+
"health": {
|
31
|
+
"name": "health",
|
32
|
+
"summary": "Check server health",
|
33
|
+
"description": "Check server health",
|
34
|
+
"params": {},
|
35
|
+
"examples": []
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
# Execute command
|
40
|
+
command = HelpCommand()
|
41
|
+
result = await command.execute()
|
42
|
+
|
43
|
+
# Check result
|
44
|
+
assert isinstance(result, HelpResult)
|
45
|
+
assert result.commands_info is not None
|
46
|
+
assert result.command_info is None
|
47
|
+
|
48
|
+
# Check content
|
49
|
+
commands_dict = result.to_dict()
|
50
|
+
assert "commands" in commands_dict
|
51
|
+
assert "help" in commands_dict["commands"]
|
52
|
+
assert "health" in commands_dict["commands"]
|
53
|
+
assert "summary" in commands_dict["commands"]["help"]
|
54
|
+
assert "Get help information" in commands_dict["commands"]["help"]["summary"]
|
55
|
+
|
56
|
+
|
57
|
+
async def test_help_command_with_cmdname(mock_registry):
|
58
|
+
"""Test help command with cmdname parameter."""
|
59
|
+
# Setup mocks
|
60
|
+
mock_registry.get_command_metadata.return_value = {
|
61
|
+
"name": "health",
|
62
|
+
"description": "Check server health",
|
63
|
+
"summary": "Check server health",
|
64
|
+
"params": {
|
65
|
+
"check_type": {
|
66
|
+
"type": "string",
|
67
|
+
"description": "Type of health check",
|
68
|
+
"required": False,
|
69
|
+
"default": "basic"
|
70
|
+
}
|
71
|
+
},
|
72
|
+
"examples": []
|
73
|
+
}
|
74
|
+
|
75
|
+
# Execute command
|
76
|
+
command = HelpCommand()
|
77
|
+
result = await command.execute(cmdname="health")
|
78
|
+
|
79
|
+
# Check result
|
80
|
+
assert isinstance(result, HelpResult)
|
81
|
+
assert result.commands_info is None
|
82
|
+
assert result.command_info is not None
|
83
|
+
|
84
|
+
# Check content
|
85
|
+
command_dict = result.to_dict()
|
86
|
+
assert "cmdname" in command_dict
|
87
|
+
assert command_dict["cmdname"] == "health"
|
88
|
+
assert "info" in command_dict
|
89
|
+
assert "description" in command_dict["info"]
|
90
|
+
assert "Check server health" in command_dict["info"]["description"]
|
91
|
+
assert "params" in command_dict["info"]
|
92
|
+
assert "check_type" in command_dict["info"]["params"]
|
93
|
+
|
94
|
+
|
95
|
+
async def test_help_command_with_invalid_cmdname(mock_registry):
|
96
|
+
"""Test help command with invalid cmdname parameter."""
|
97
|
+
# Setup mocks
|
98
|
+
mock_registry.get_command_metadata.side_effect = NotFoundError("Command not found")
|
99
|
+
|
100
|
+
# Execute command and check exception
|
101
|
+
command = HelpCommand()
|
102
|
+
with pytest.raises(NotFoundError) as excinfo:
|
103
|
+
await command.execute(cmdname="non_existent")
|
104
|
+
|
105
|
+
assert "not found" in str(excinfo.value)
|
106
|
+
|
107
|
+
|
108
|
+
def test_help_result_schema():
|
109
|
+
"""Test help command result schema."""
|
110
|
+
schema = HelpResult.get_schema()
|
111
|
+
|
112
|
+
assert schema["type"] == "object"
|
113
|
+
assert "oneOf" in schema
|
114
|
+
assert len(schema["oneOf"]) == 2
|
115
|
+
|
116
|
+
commands_schema = schema["oneOf"][0]
|
117
|
+
assert "properties" in commands_schema
|
118
|
+
assert "commands" in commands_schema["properties"]
|
119
|
+
|
120
|
+
command_schema = schema["oneOf"][1]
|
121
|
+
assert "properties" in command_schema
|
122
|
+
assert "cmdname" in command_schema["properties"]
|
123
|
+
assert "info" in command_schema["properties"]
|
124
|
+
|
125
|
+
|
126
|
+
def test_help_command_schema():
|
127
|
+
"""Test help command schema."""
|
128
|
+
schema = HelpCommand.get_schema()
|
129
|
+
|
130
|
+
assert schema["type"] == "object"
|
131
|
+
assert "properties" in schema
|
132
|
+
assert "cmdname" in schema["properties"]
|
133
|
+
assert schema["properties"]["cmdname"]["type"] == "string"
|
@@ -0,0 +1,131 @@
|
|
1
|
+
"""
|
2
|
+
Module with fixtures and configuration for tests.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import json
|
6
|
+
import os
|
7
|
+
import tempfile
|
8
|
+
from typing import Any, Dict, Generator
|
9
|
+
|
10
|
+
import pytest
|
11
|
+
from fastapi.testclient import TestClient
|
12
|
+
|
13
|
+
from mcp_proxy_adapter.api.app import create_app
|
14
|
+
from mcp_proxy_adapter.commands.command_registry import registry
|
15
|
+
from mcp_proxy_adapter.config import Config
|
16
|
+
|
17
|
+
|
18
|
+
@pytest.fixture
|
19
|
+
def temp_config_file() -> Generator[str, None, None]:
|
20
|
+
"""
|
21
|
+
Creates temporary configuration file for tests.
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
Path to temporary configuration file.
|
25
|
+
"""
|
26
|
+
# Create temporary file
|
27
|
+
fd, path = tempfile.mkstemp(suffix=".json")
|
28
|
+
|
29
|
+
# Write test configuration
|
30
|
+
test_config = {
|
31
|
+
"server": {
|
32
|
+
"host": "127.0.0.1",
|
33
|
+
"port": 8888
|
34
|
+
},
|
35
|
+
"logging": {
|
36
|
+
"level": "DEBUG",
|
37
|
+
"file": None
|
38
|
+
},
|
39
|
+
# Отключаем аутентификацию и ограничение скорости для тестов
|
40
|
+
"auth_enabled": False,
|
41
|
+
"rate_limit_enabled": False
|
42
|
+
}
|
43
|
+
|
44
|
+
with os.fdopen(fd, "w") as f:
|
45
|
+
json.dump(test_config, f)
|
46
|
+
|
47
|
+
yield path
|
48
|
+
|
49
|
+
# Remove temporary file after tests
|
50
|
+
os.unlink(path)
|
51
|
+
|
52
|
+
|
53
|
+
@pytest.fixture
|
54
|
+
def test_config(temp_config_file: str) -> Config:
|
55
|
+
"""
|
56
|
+
Creates test configuration instance.
|
57
|
+
|
58
|
+
Args:
|
59
|
+
temp_config_file: Path to temporary configuration file.
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
Test configuration instance.
|
63
|
+
"""
|
64
|
+
return Config(temp_config_file)
|
65
|
+
|
66
|
+
|
67
|
+
@pytest.fixture
|
68
|
+
def test_client() -> TestClient:
|
69
|
+
"""
|
70
|
+
Creates test client for FastAPI application.
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
FastAPI test client.
|
74
|
+
"""
|
75
|
+
app = create_app()
|
76
|
+
return TestClient(app)
|
77
|
+
|
78
|
+
|
79
|
+
@pytest.fixture
|
80
|
+
def clean_registry() -> Generator[None, None, None]:
|
81
|
+
"""
|
82
|
+
Cleans command registry before test and restores it after.
|
83
|
+
|
84
|
+
Yields:
|
85
|
+
None
|
86
|
+
"""
|
87
|
+
# Save current commands
|
88
|
+
original_commands = dict(registry._commands)
|
89
|
+
|
90
|
+
# Clear registry
|
91
|
+
registry.clear()
|
92
|
+
|
93
|
+
yield
|
94
|
+
|
95
|
+
# Restore registry
|
96
|
+
registry.clear()
|
97
|
+
for name, command in original_commands.items():
|
98
|
+
registry._commands[name] = command
|
99
|
+
|
100
|
+
|
101
|
+
@pytest.fixture
|
102
|
+
def json_rpc_request() -> Dict[str, Any]:
|
103
|
+
"""
|
104
|
+
Creates base JSON-RPC request.
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
Dictionary with JSON-RPC request data.
|
108
|
+
"""
|
109
|
+
return {
|
110
|
+
"jsonrpc": "2.0",
|
111
|
+
"method": "test_command",
|
112
|
+
"params": {},
|
113
|
+
"id": "test-id"
|
114
|
+
}
|
115
|
+
|
116
|
+
|
117
|
+
@pytest.fixture(autouse=True)
|
118
|
+
def register_test_commands():
|
119
|
+
"""
|
120
|
+
Регистрирует тестовые команды в registry для всех тестов.
|
121
|
+
"""
|
122
|
+
from mcp_proxy_adapter.tests.stubs.echo_command import EchoCommand
|
123
|
+
from mcp_proxy_adapter.commands.command_registry import registry
|
124
|
+
|
125
|
+
# Регистрируем команды для тестирования
|
126
|
+
registry.register(EchoCommand)
|
127
|
+
|
128
|
+
yield
|
129
|
+
|
130
|
+
# Очищаем registry после тестов
|
131
|
+
registry.clear()
|
@@ -0,0 +1,253 @@
|
|
1
|
+
"""
|
2
|
+
Functional tests for the API.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
from typing import Dict, Any
|
7
|
+
|
8
|
+
from fastapi.testclient import TestClient
|
9
|
+
|
10
|
+
from mcp_proxy_adapter.commands.command_registry import registry
|
11
|
+
from mcp_proxy_adapter.tests.stubs.echo_command import EchoCommand
|
12
|
+
|
13
|
+
|
14
|
+
@pytest.fixture
|
15
|
+
def register_echo_command(clean_registry):
|
16
|
+
"""
|
17
|
+
Fixture to register the Echo command for testing.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
clean_registry: Fixture to clean registry before and after test.
|
21
|
+
"""
|
22
|
+
registry.register(EchoCommand)
|
23
|
+
yield
|
24
|
+
registry.clear()
|
25
|
+
|
26
|
+
|
27
|
+
@pytest.mark.functional
|
28
|
+
def test_execute_command(test_client: TestClient, json_rpc_request: Dict[str, Any], register_echo_command):
|
29
|
+
"""
|
30
|
+
Test execution of command via API.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
test_client: FastAPI test client.
|
34
|
+
json_rpc_request: Base JSON-RPC request.
|
35
|
+
register_echo_command: Fixture to register test command.
|
36
|
+
"""
|
37
|
+
# Create JSON-RPC request
|
38
|
+
request_data = json_rpc_request.copy()
|
39
|
+
request_data["method"] = "echo"
|
40
|
+
request_data["params"] = {"test_key": "test_value"}
|
41
|
+
|
42
|
+
# Send request
|
43
|
+
response = test_client.post("/api/jsonrpc", json=request_data)
|
44
|
+
|
45
|
+
# Check response
|
46
|
+
assert response.status_code == 200
|
47
|
+
assert response.headers["content-type"] == "application/json"
|
48
|
+
|
49
|
+
# Check response structure
|
50
|
+
data = response.json()
|
51
|
+
assert "jsonrpc" in data and data["jsonrpc"] == "2.0"
|
52
|
+
assert "result" in data
|
53
|
+
assert "id" in data and data["id"] == request_data["id"]
|
54
|
+
|
55
|
+
# Check result content
|
56
|
+
assert "params" in data["result"]
|
57
|
+
assert data["result"]["params"] == {"test_key": "test_value"}
|
58
|
+
|
59
|
+
|
60
|
+
@pytest.mark.functional
|
61
|
+
def test_execute_nonexistent_command(test_client: TestClient, json_rpc_request: Dict[str, Any]):
|
62
|
+
"""
|
63
|
+
Test execution of nonexistent command.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
test_client: FastAPI test client.
|
67
|
+
json_rpc_request: Base JSON-RPC request.
|
68
|
+
"""
|
69
|
+
# Create JSON-RPC request with nonexistent command
|
70
|
+
request_data = json_rpc_request.copy()
|
71
|
+
request_data["method"] = "nonexistent_command"
|
72
|
+
|
73
|
+
# Send request
|
74
|
+
response = test_client.post("/api/jsonrpc", json=request_data)
|
75
|
+
|
76
|
+
# Check response
|
77
|
+
assert response.status_code == 400 or response.status_code == 200
|
78
|
+
|
79
|
+
# Check response structure
|
80
|
+
data = response.json()
|
81
|
+
assert "jsonrpc" in data and data["jsonrpc"] == "2.0"
|
82
|
+
assert "error" in data
|
83
|
+
assert "id" in data and data["id"] == request_data["id"]
|
84
|
+
|
85
|
+
# Check error content
|
86
|
+
assert "code" in data["error"]
|
87
|
+
assert "message" in data["error"]
|
88
|
+
assert data["error"]["code"] == -32601 # Method not found
|
89
|
+
assert "not found" in data["error"]["message"].lower()
|
90
|
+
|
91
|
+
|
92
|
+
@pytest.mark.functional
|
93
|
+
def test_invalid_json_rpc_request(test_client: TestClient):
|
94
|
+
"""
|
95
|
+
Test invalid JSON-RPC request.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
test_client: FastAPI test client.
|
99
|
+
"""
|
100
|
+
# Create invalid JSON-RPC request
|
101
|
+
request_data = {
|
102
|
+
"method": "echo",
|
103
|
+
"params": {}
|
104
|
+
# Missing jsonrpc and id fields
|
105
|
+
}
|
106
|
+
|
107
|
+
# Send request
|
108
|
+
response = test_client.post("/api/jsonrpc", json=request_data)
|
109
|
+
|
110
|
+
# Check response
|
111
|
+
assert response.status_code == 400 or response.status_code == 200
|
112
|
+
|
113
|
+
# Check response structure
|
114
|
+
data = response.json()
|
115
|
+
assert "jsonrpc" in data and data["jsonrpc"] == "2.0"
|
116
|
+
assert "error" in data
|
117
|
+
|
118
|
+
# Check error content
|
119
|
+
assert "code" in data["error"]
|
120
|
+
assert "message" in data["error"]
|
121
|
+
assert data["error"]["code"] == -32600 # Invalid Request
|
122
|
+
|
123
|
+
|
124
|
+
@pytest.mark.functional
|
125
|
+
def test_get_commands(test_client: TestClient, register_echo_command):
|
126
|
+
"""
|
127
|
+
Test getting list of commands.
|
128
|
+
|
129
|
+
Args:
|
130
|
+
test_client: FastAPI test client.
|
131
|
+
register_echo_command: Fixture to register test command.
|
132
|
+
"""
|
133
|
+
# Send request
|
134
|
+
response = test_client.get("/api/commands")
|
135
|
+
|
136
|
+
# Check response
|
137
|
+
assert response.status_code == 200
|
138
|
+
|
139
|
+
# Check response structure
|
140
|
+
data = response.json()
|
141
|
+
assert "commands" in data
|
142
|
+
assert isinstance(data["commands"], dict)
|
143
|
+
|
144
|
+
# Check that echo command is in the list
|
145
|
+
assert "echo" in data["commands"]
|
146
|
+
assert "description" in data["commands"]["echo"]
|
147
|
+
assert "schema" in data["commands"]["echo"]
|
148
|
+
|
149
|
+
|
150
|
+
@pytest.mark.functional
|
151
|
+
def test_health_check(test_client: TestClient):
|
152
|
+
"""
|
153
|
+
Test health check endpoint.
|
154
|
+
|
155
|
+
Args:
|
156
|
+
test_client: FastAPI test client.
|
157
|
+
"""
|
158
|
+
# Send request
|
159
|
+
response = test_client.get("/health")
|
160
|
+
|
161
|
+
# Check response
|
162
|
+
assert response.status_code == 200
|
163
|
+
|
164
|
+
# Check response structure
|
165
|
+
data = response.json()
|
166
|
+
assert "status" in data
|
167
|
+
assert data["status"] == "ok"
|
168
|
+
|
169
|
+
|
170
|
+
@pytest.mark.functional
|
171
|
+
def test_openapi_schema(test_client: TestClient):
|
172
|
+
"""
|
173
|
+
Test OpenAPI schema endpoint.
|
174
|
+
|
175
|
+
Args:
|
176
|
+
test_client: FastAPI test client.
|
177
|
+
"""
|
178
|
+
# Send request
|
179
|
+
response = test_client.get("/openapi.json")
|
180
|
+
|
181
|
+
# Check response
|
182
|
+
assert response.status_code == 200
|
183
|
+
|
184
|
+
# Check response content
|
185
|
+
data = response.json()
|
186
|
+
assert "openapi" in data
|
187
|
+
assert "info" in data
|
188
|
+
assert "paths" in data
|
189
|
+
|
190
|
+
# Check that API endpoints are in schema
|
191
|
+
assert "/cmd" in data["paths"]
|
192
|
+
assert "/api/commands" in data["paths"]
|
193
|
+
assert "/health" in data["paths"]
|
194
|
+
|
195
|
+
|
196
|
+
@pytest.mark.functional
|
197
|
+
def test_batch_requests(test_client: TestClient, json_rpc_request: Dict[str, Any], register_echo_command):
|
198
|
+
"""
|
199
|
+
Test batch requests processing.
|
200
|
+
|
201
|
+
Args:
|
202
|
+
test_client: FastAPI test client.
|
203
|
+
json_rpc_request: Base JSON-RPC request.
|
204
|
+
register_echo_command: Fixture to register test command.
|
205
|
+
"""
|
206
|
+
# Create batch request
|
207
|
+
request1 = json_rpc_request.copy()
|
208
|
+
request1["method"] = "echo"
|
209
|
+
request1["params"] = {"request_id": "1"}
|
210
|
+
request1["id"] = "1"
|
211
|
+
|
212
|
+
request2 = json_rpc_request.copy()
|
213
|
+
request2["method"] = "echo"
|
214
|
+
request2["params"] = {"request_id": "2"}
|
215
|
+
request2["id"] = "2"
|
216
|
+
|
217
|
+
batch_request = [request1, request2]
|
218
|
+
|
219
|
+
# Send request
|
220
|
+
response = test_client.post("/api/jsonrpc", json=batch_request)
|
221
|
+
|
222
|
+
# Check response
|
223
|
+
assert response.status_code == 200
|
224
|
+
|
225
|
+
# Check response structure
|
226
|
+
data = response.json()
|
227
|
+
assert isinstance(data, list)
|
228
|
+
assert len(data) == 2
|
229
|
+
|
230
|
+
# Check individual responses
|
231
|
+
assert data[0]["result"]["params"] == {"request_id": "1"}
|
232
|
+
assert data[0]["id"] == "1"
|
233
|
+
|
234
|
+
assert data[1]["result"]["params"] == {"request_id": "2"}
|
235
|
+
assert data[1]["id"] == "2"
|
236
|
+
|
237
|
+
|
238
|
+
def test_custom_openapi_schema_fields():
|
239
|
+
"""
|
240
|
+
Test that custom title, description, and version are set in the OpenAPI schema.
|
241
|
+
"""
|
242
|
+
from fastapi import FastAPI
|
243
|
+
from mcp_proxy_adapter.custom_openapi import custom_openapi
|
244
|
+
|
245
|
+
app = FastAPI(
|
246
|
+
title="Custom Title",
|
247
|
+
description="Custom Description",
|
248
|
+
version="9.9.9"
|
249
|
+
)
|
250
|
+
schema = custom_openapi(app)
|
251
|
+
assert schema["info"]["title"] == "Custom Title"
|
252
|
+
assert schema["info"]["description"] == "Custom Description"
|
253
|
+
assert schema["info"]["version"] == "9.9.9"
|