napistu 0.2.5.dev6__py3-none-any.whl → 0.3.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.
- napistu/__main__.py +126 -96
- napistu/constants.py +35 -41
- napistu/context/__init__.py +10 -0
- napistu/context/discretize.py +462 -0
- napistu/context/filtering.py +387 -0
- napistu/gcs/__init__.py +1 -1
- napistu/identifiers.py +74 -15
- napistu/indices.py +68 -0
- napistu/ingestion/__init__.py +1 -1
- napistu/ingestion/bigg.py +47 -62
- napistu/ingestion/constants.py +18 -133
- napistu/ingestion/gtex.py +113 -0
- napistu/ingestion/hpa.py +147 -0
- napistu/ingestion/sbml.py +0 -97
- napistu/ingestion/string.py +2 -2
- napistu/matching/__init__.py +10 -0
- napistu/matching/constants.py +18 -0
- napistu/matching/interactions.py +518 -0
- napistu/matching/mount.py +529 -0
- napistu/matching/species.py +510 -0
- napistu/mcp/__init__.py +7 -4
- napistu/mcp/__main__.py +128 -72
- napistu/mcp/client.py +16 -25
- napistu/mcp/codebase.py +201 -153
- napistu/mcp/component_base.py +170 -0
- napistu/mcp/config.py +223 -0
- napistu/mcp/constants.py +45 -2
- napistu/mcp/documentation.py +253 -136
- napistu/mcp/documentation_utils.py +13 -48
- napistu/mcp/execution.py +372 -305
- napistu/mcp/health.py +49 -67
- napistu/mcp/profiles.py +10 -6
- napistu/mcp/server.py +161 -80
- napistu/mcp/tutorials.py +139 -87
- napistu/modify/__init__.py +1 -1
- napistu/modify/gaps.py +1 -1
- napistu/network/__init__.py +1 -1
- napistu/network/constants.py +101 -34
- napistu/network/data_handling.py +388 -0
- napistu/network/ig_utils.py +351 -0
- napistu/network/napistu_graph_core.py +354 -0
- napistu/network/neighborhoods.py +40 -40
- napistu/network/net_create.py +373 -309
- napistu/network/net_propagation.py +47 -19
- napistu/network/{net_utils.py → ng_utils.py} +124 -272
- napistu/network/paths.py +67 -51
- napistu/network/precompute.py +11 -11
- napistu/ontologies/__init__.py +10 -0
- napistu/ontologies/constants.py +129 -0
- napistu/ontologies/dogma.py +243 -0
- napistu/ontologies/genodexito.py +649 -0
- napistu/ontologies/mygene.py +369 -0
- napistu/ontologies/renaming.py +198 -0
- napistu/rpy2/__init__.py +229 -86
- napistu/rpy2/callr.py +47 -77
- napistu/rpy2/constants.py +24 -23
- napistu/rpy2/rids.py +61 -648
- napistu/sbml_dfs_core.py +587 -222
- napistu/scverse/__init__.py +15 -0
- napistu/scverse/constants.py +28 -0
- napistu/scverse/loading.py +727 -0
- napistu/utils.py +118 -10
- {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/METADATA +8 -3
- napistu-0.3.1.dist-info/RECORD +133 -0
- tests/conftest.py +22 -0
- tests/test_context_discretize.py +56 -0
- tests/test_context_filtering.py +267 -0
- tests/test_identifiers.py +100 -0
- tests/test_indices.py +65 -0
- tests/{test_edgelist.py → test_ingestion_napistu_edgelist.py} +2 -2
- tests/test_matching_interactions.py +108 -0
- tests/test_matching_mount.py +305 -0
- tests/test_matching_species.py +394 -0
- tests/test_mcp_config.py +193 -0
- tests/test_mcp_documentation_utils.py +12 -3
- tests/test_mcp_server.py +356 -0
- tests/test_network_data_handling.py +397 -0
- tests/test_network_ig_utils.py +23 -0
- tests/test_network_neighborhoods.py +19 -0
- tests/test_network_net_create.py +459 -0
- tests/test_network_ng_utils.py +30 -0
- tests/test_network_paths.py +56 -0
- tests/{test_precomputed_distances.py → test_network_precompute.py} +8 -6
- tests/test_ontologies_genodexito.py +58 -0
- tests/test_ontologies_mygene.py +39 -0
- tests/test_ontologies_renaming.py +110 -0
- tests/test_rpy2_callr.py +79 -0
- tests/test_rpy2_init.py +151 -0
- tests/test_sbml.py +0 -31
- tests/test_sbml_dfs_core.py +134 -10
- tests/test_scverse_loading.py +778 -0
- tests/test_set_coverage.py +2 -2
- tests/test_utils.py +121 -1
- napistu/mechanism_matching.py +0 -1353
- napistu/rpy2/netcontextr.py +0 -467
- napistu-0.2.5.dev6.dist-info/RECORD +0 -97
- tests/test_igraph.py +0 -367
- tests/test_mechanism_matching.py +0 -784
- tests/test_net_utils.py +0 -149
- tests/test_netcontextr.py +0 -105
- tests/test_rpy2.py +0 -61
- /napistu/ingestion/{cpr_edgelist.py → napistu_edgelist.py} +0 -0
- {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/WHEEL +0 -0
- {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/entry_points.txt +0 -0
- {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/top_level.txt +0 -0
- /tests/{test_obo.py → test_ingestion_obo.py} +0 -0
tests/test_mcp_config.py
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
"""
|
2
|
+
Tests for MCP configuration validation, presets, and Pydantic models.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
from pydantic import ValidationError
|
7
|
+
import click
|
8
|
+
|
9
|
+
from napistu.mcp.config import (
|
10
|
+
MCPServerConfig,
|
11
|
+
MCPClientConfig,
|
12
|
+
local_server_config,
|
13
|
+
production_server_config,
|
14
|
+
local_client_config,
|
15
|
+
production_client_config,
|
16
|
+
validate_server_config_flags,
|
17
|
+
validate_client_config_flags,
|
18
|
+
)
|
19
|
+
from napistu.mcp.constants import MCP_DEFAULTS
|
20
|
+
|
21
|
+
|
22
|
+
def test_configuration_validation():
|
23
|
+
"""Test validation logic for preset conflicts and manual configuration requirements."""
|
24
|
+
|
25
|
+
# Test conflicting presets
|
26
|
+
with pytest.raises(
|
27
|
+
click.BadParameter, match="Cannot use both --local and --production"
|
28
|
+
):
|
29
|
+
validate_server_config_flags(
|
30
|
+
local=True, production=True, host=None, port=None, server_name=None
|
31
|
+
)
|
32
|
+
|
33
|
+
with pytest.raises(
|
34
|
+
click.BadParameter, match="Cannot use both --local and --production"
|
35
|
+
):
|
36
|
+
validate_client_config_flags(
|
37
|
+
local=True, production=True, host=None, port=None, use_https=None
|
38
|
+
)
|
39
|
+
|
40
|
+
# Test preset + manual config conflicts
|
41
|
+
with pytest.raises(click.BadParameter, match="Cannot use --local with manual"):
|
42
|
+
validate_server_config_flags(
|
43
|
+
local=True,
|
44
|
+
production=False,
|
45
|
+
host="192.168.1.1",
|
46
|
+
port=None,
|
47
|
+
server_name=None,
|
48
|
+
)
|
49
|
+
|
50
|
+
with pytest.raises(click.BadParameter, match="Cannot use --production with manual"):
|
51
|
+
validate_client_config_flags(
|
52
|
+
local=False, production=True, host="localhost", port=None, use_https=None
|
53
|
+
)
|
54
|
+
|
55
|
+
# Test incomplete manual configuration
|
56
|
+
with pytest.raises(
|
57
|
+
click.BadParameter,
|
58
|
+
match="Manual configuration requires --host, --port, and --server-name",
|
59
|
+
):
|
60
|
+
validate_server_config_flags(
|
61
|
+
local=False, production=False, host="localhost", port=None, server_name=None
|
62
|
+
)
|
63
|
+
|
64
|
+
with pytest.raises(
|
65
|
+
click.BadParameter,
|
66
|
+
match="Manual configuration requires --host, --port, and --https",
|
67
|
+
):
|
68
|
+
validate_client_config_flags(
|
69
|
+
local=False, production=False, host="localhost", port=8080, use_https=None
|
70
|
+
)
|
71
|
+
|
72
|
+
# Test valid configurations don't raise errors
|
73
|
+
config = validate_server_config_flags(
|
74
|
+
local=True, production=False, host=None, port=None, server_name=None
|
75
|
+
)
|
76
|
+
assert isinstance(config, MCPServerConfig)
|
77
|
+
|
78
|
+
config = validate_client_config_flags(
|
79
|
+
local=False, production=True, host=None, port=None, use_https=None
|
80
|
+
)
|
81
|
+
assert isinstance(config, MCPClientConfig)
|
82
|
+
|
83
|
+
# Test valid manual configuration
|
84
|
+
config = validate_server_config_flags(
|
85
|
+
local=False,
|
86
|
+
production=False,
|
87
|
+
host="localhost",
|
88
|
+
port=9000,
|
89
|
+
server_name="test-server",
|
90
|
+
)
|
91
|
+
assert config.host == "localhost"
|
92
|
+
assert config.port == 9000
|
93
|
+
assert config.server_name == "test-server"
|
94
|
+
|
95
|
+
|
96
|
+
def test_preset_configurations():
|
97
|
+
"""Test that preset functions return correct and consistent configurations."""
|
98
|
+
|
99
|
+
# Test local server preset
|
100
|
+
local_server = local_server_config()
|
101
|
+
assert local_server.host == MCP_DEFAULTS.LOCAL_HOST
|
102
|
+
assert local_server.port == MCP_DEFAULTS.LOCAL_PORT
|
103
|
+
assert local_server.server_name == MCP_DEFAULTS.LOCAL_SERVER_NAME
|
104
|
+
assert (
|
105
|
+
local_server.bind_address
|
106
|
+
== f"{MCP_DEFAULTS.LOCAL_HOST}:{MCP_DEFAULTS.LOCAL_PORT}"
|
107
|
+
)
|
108
|
+
|
109
|
+
# Test production server preset
|
110
|
+
production_server = production_server_config()
|
111
|
+
assert production_server.host == MCP_DEFAULTS.PRODUCTION_HOST
|
112
|
+
assert production_server.port == MCP_DEFAULTS.PRODUCTION_PORT
|
113
|
+
assert production_server.server_name == MCP_DEFAULTS.PRODUCTION_SERVER_NAME
|
114
|
+
|
115
|
+
# Test local client preset
|
116
|
+
local_client = local_client_config()
|
117
|
+
assert local_client.host == MCP_DEFAULTS.LOCAL_HOST
|
118
|
+
assert local_client.port == MCP_DEFAULTS.LOCAL_PORT
|
119
|
+
assert not local_client.use_https
|
120
|
+
assert (
|
121
|
+
local_client.base_url
|
122
|
+
== f"http://{MCP_DEFAULTS.LOCAL_HOST}:{MCP_DEFAULTS.LOCAL_PORT}"
|
123
|
+
)
|
124
|
+
assert (
|
125
|
+
local_client.mcp_url
|
126
|
+
== f"http://{MCP_DEFAULTS.LOCAL_HOST}:{MCP_DEFAULTS.LOCAL_PORT}{MCP_DEFAULTS.MCP_PATH}"
|
127
|
+
)
|
128
|
+
|
129
|
+
# Test production client preset
|
130
|
+
production_client = production_client_config()
|
131
|
+
assert production_client.use_https
|
132
|
+
assert production_client.port == MCP_DEFAULTS.HTTPS_PORT
|
133
|
+
assert (
|
134
|
+
"napistu-mcp-server" in production_client.host
|
135
|
+
) # Extracted from production URL
|
136
|
+
assert production_client.base_url.startswith("https://")
|
137
|
+
assert production_client.mcp_url.endswith(MCP_DEFAULTS.MCP_PATH)
|
138
|
+
|
139
|
+
# Test that presets are deterministic (same result on multiple calls)
|
140
|
+
assert local_server_config().host == local_server_config().host
|
141
|
+
assert production_client_config().port == production_client_config().port
|
142
|
+
|
143
|
+
|
144
|
+
def test_pydantic_model_validation():
|
145
|
+
"""Test Pydantic model validation for ports, hosts, and computed properties."""
|
146
|
+
|
147
|
+
# Test valid configurations
|
148
|
+
valid_server = MCPServerConfig(host="localhost", port=8080, server_name="test")
|
149
|
+
assert valid_server.host == "localhost"
|
150
|
+
assert valid_server.port == 8080
|
151
|
+
assert valid_server.server_name == "test"
|
152
|
+
assert valid_server.bind_address == "localhost:8080"
|
153
|
+
|
154
|
+
valid_client = MCPClientConfig(host="example.com", port=443, use_https=True)
|
155
|
+
assert valid_client.host == "example.com"
|
156
|
+
assert valid_client.base_url == "https://example.com:443"
|
157
|
+
assert valid_client.mcp_url == f"https://example.com:443{MCP_DEFAULTS.MCP_PATH}"
|
158
|
+
|
159
|
+
# Test invalid port validation
|
160
|
+
with pytest.raises(
|
161
|
+
ValidationError, match="Input should be greater than or equal to 1"
|
162
|
+
):
|
163
|
+
MCPServerConfig(host="localhost", port=0, server_name="test")
|
164
|
+
|
165
|
+
with pytest.raises(
|
166
|
+
ValidationError, match="Input should be less than or equal to 65535"
|
167
|
+
):
|
168
|
+
MCPServerConfig(host="localhost", port=70000, server_name="test")
|
169
|
+
|
170
|
+
with pytest.raises(
|
171
|
+
ValidationError, match="Input should be greater than or equal to 1"
|
172
|
+
):
|
173
|
+
MCPClientConfig(host="localhost", port=-1, use_https=False)
|
174
|
+
|
175
|
+
# Test empty/whitespace host validation
|
176
|
+
with pytest.raises(ValidationError, match="Host cannot be empty"):
|
177
|
+
MCPServerConfig(host="", port=8080, server_name="test")
|
178
|
+
|
179
|
+
with pytest.raises(ValidationError, match="Host cannot be empty"):
|
180
|
+
MCPServerConfig(host=" ", port=8080, server_name="test")
|
181
|
+
|
182
|
+
# Test host whitespace trimming
|
183
|
+
server_with_spaces = MCPServerConfig(
|
184
|
+
host=" localhost ", port=8080, server_name="test"
|
185
|
+
)
|
186
|
+
assert server_with_spaces.host == "localhost" # Should be trimmed
|
187
|
+
|
188
|
+
# Test URL generation with different protocols
|
189
|
+
http_client = MCPClientConfig(host="localhost", port=8080, use_https=False)
|
190
|
+
assert http_client.base_url == "http://localhost:8080"
|
191
|
+
|
192
|
+
https_client = MCPClientConfig(host="secure.com", port=443, use_https=True)
|
193
|
+
assert https_client.base_url == "https://secure.com:443"
|
@@ -1,13 +1,22 @@
|
|
1
1
|
import pytest
|
2
|
-
from napistu.mcp.documentation_utils import load_readme_content
|
3
|
-
from napistu.mcp.constants import READMES
|
2
|
+
from napistu.mcp.documentation_utils import load_readme_content, fetch_wiki_page
|
3
|
+
from napistu.mcp.constants import READMES, WIKI_PAGES
|
4
4
|
|
5
5
|
|
6
6
|
@pytest.mark.asyncio
|
7
|
-
@pytest.mark.parametrize("name,url", READMES.items())
|
7
|
+
@pytest.mark.parametrize("name,url", list(READMES.items())[:2])
|
8
8
|
async def test_load_readme_content(name, url):
|
9
9
|
content = await load_readme_content(url)
|
10
10
|
assert isinstance(content, str)
|
11
11
|
assert len(content) > 0
|
12
12
|
# Optionally, check for a keyword in the content
|
13
13
|
assert "napistu" in content.lower() or "Napistu" in content
|
14
|
+
|
15
|
+
|
16
|
+
@pytest.mark.asyncio
|
17
|
+
@pytest.mark.parametrize("page_name", list(WIKI_PAGES)[:2])
|
18
|
+
async def test_fetch_wiki_page(page_name):
|
19
|
+
content = await fetch_wiki_page(page_name)
|
20
|
+
assert isinstance(content, str)
|
21
|
+
assert len(content) > 0
|
22
|
+
assert "napistu" in content.lower() or "Napistu" in content
|
tests/test_mcp_server.py
ADDED
@@ -0,0 +1,356 @@
|
|
1
|
+
"""
|
2
|
+
Tests to validate MCP tool and resource naming conventions.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import re
|
6
|
+
from typing import List, Tuple
|
7
|
+
from fastmcp import FastMCP
|
8
|
+
|
9
|
+
from napistu.mcp import (
|
10
|
+
documentation,
|
11
|
+
codebase,
|
12
|
+
tutorials,
|
13
|
+
execution,
|
14
|
+
health,
|
15
|
+
)
|
16
|
+
|
17
|
+
# Regex patterns for validation
|
18
|
+
VALID_RESOURCE_PATH = re.compile(
|
19
|
+
r"^napistu://[a-zA-Z][a-zA-Z0-9_]*(?:/[a-zA-Z0-9_{}.-]+)*$"
|
20
|
+
)
|
21
|
+
VALID_TOOL_NAME = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]*$")
|
22
|
+
|
23
|
+
|
24
|
+
def collect_resources_and_tools(mcp: FastMCP) -> Tuple[List[str], List[str]]:
|
25
|
+
"""
|
26
|
+
Collect all registered resource paths and tool names from an MCP server instance.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
mcp: FastMCP server instance
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
Tuple of (resource_paths, tool_names)
|
33
|
+
"""
|
34
|
+
# Get all registered resources and tools
|
35
|
+
resources = mcp._resource_manager.get_resources()
|
36
|
+
tool_names = mcp._tool_manager.get_tools()
|
37
|
+
|
38
|
+
# Extract resource paths from the resource objects
|
39
|
+
resource_paths = []
|
40
|
+
|
41
|
+
# Add all resources (including parameterized ones)
|
42
|
+
for resource in resources.values():
|
43
|
+
resource_paths.append(str(resource.uri))
|
44
|
+
|
45
|
+
return resource_paths, tool_names
|
46
|
+
|
47
|
+
|
48
|
+
def test_documentation_naming():
|
49
|
+
"""Test that documentation component uses valid names."""
|
50
|
+
mcp = FastMCP("test")
|
51
|
+
documentation_component = documentation.get_component()
|
52
|
+
documentation_component.register(mcp)
|
53
|
+
|
54
|
+
resource_paths, tool_names = collect_resources_and_tools(mcp)
|
55
|
+
|
56
|
+
# Check resource paths
|
57
|
+
for path in resource_paths:
|
58
|
+
assert VALID_RESOURCE_PATH.match(path), f"Invalid resource path: {path}"
|
59
|
+
|
60
|
+
# Check tool names
|
61
|
+
for name in tool_names:
|
62
|
+
assert VALID_TOOL_NAME.match(name), f"Invalid tool name: {name}"
|
63
|
+
|
64
|
+
|
65
|
+
def test_codebase_naming():
|
66
|
+
"""Test that codebase component uses valid names."""
|
67
|
+
mcp = FastMCP("test")
|
68
|
+
codebase_component = codebase.get_component()
|
69
|
+
codebase_component.register(mcp)
|
70
|
+
|
71
|
+
resource_paths, tool_names = collect_resources_and_tools(mcp)
|
72
|
+
|
73
|
+
# Check resource paths
|
74
|
+
for path in resource_paths:
|
75
|
+
assert VALID_RESOURCE_PATH.match(path), f"Invalid resource path: {path}"
|
76
|
+
|
77
|
+
# Check tool names
|
78
|
+
for name in tool_names:
|
79
|
+
assert VALID_TOOL_NAME.match(name), f"Invalid tool name: {name}"
|
80
|
+
|
81
|
+
|
82
|
+
def test_tutorials_naming():
|
83
|
+
"""Test that tutorials component uses valid names."""
|
84
|
+
mcp = FastMCP("test")
|
85
|
+
tutorials_component = tutorials.get_component()
|
86
|
+
tutorials_component.register(mcp)
|
87
|
+
|
88
|
+
resource_paths, tool_names = collect_resources_and_tools(mcp)
|
89
|
+
|
90
|
+
# Check resource paths
|
91
|
+
for path in resource_paths:
|
92
|
+
assert VALID_RESOURCE_PATH.match(path), f"Invalid resource path: {path}"
|
93
|
+
|
94
|
+
# Check tool names
|
95
|
+
for name in tool_names:
|
96
|
+
assert VALID_TOOL_NAME.match(name), f"Invalid tool name: {name}"
|
97
|
+
|
98
|
+
|
99
|
+
def test_execution_naming():
|
100
|
+
"""Test that execution component uses valid names."""
|
101
|
+
mcp = FastMCP("test")
|
102
|
+
|
103
|
+
# Create execution component with test session context
|
104
|
+
test_session_context = {}
|
105
|
+
test_object_registry = {}
|
106
|
+
execution_component = execution.create_component(
|
107
|
+
session_context=test_session_context, object_registry=test_object_registry
|
108
|
+
)
|
109
|
+
execution_component.register(mcp)
|
110
|
+
|
111
|
+
resource_paths, tool_names = collect_resources_and_tools(mcp)
|
112
|
+
|
113
|
+
# Check resource paths
|
114
|
+
for path in resource_paths:
|
115
|
+
assert VALID_RESOURCE_PATH.match(path), f"Invalid resource path: {path}"
|
116
|
+
|
117
|
+
# Check tool names
|
118
|
+
for name in tool_names:
|
119
|
+
assert VALID_TOOL_NAME.match(name), f"Invalid tool name: {name}"
|
120
|
+
|
121
|
+
|
122
|
+
def test_health_naming():
|
123
|
+
"""Test that health component uses valid names."""
|
124
|
+
mcp = FastMCP("test")
|
125
|
+
health.register_components(mcp)
|
126
|
+
|
127
|
+
resource_paths, tool_names = collect_resources_and_tools(mcp)
|
128
|
+
|
129
|
+
# Check resource paths
|
130
|
+
for path in resource_paths:
|
131
|
+
assert VALID_RESOURCE_PATH.match(path), f"Invalid resource path: {path}"
|
132
|
+
|
133
|
+
# Check tool names
|
134
|
+
for name in tool_names:
|
135
|
+
assert VALID_TOOL_NAME.match(name), f"Invalid tool name: {name}"
|
136
|
+
|
137
|
+
|
138
|
+
def test_all_components_naming():
|
139
|
+
"""Test that all components together use valid names without conflicts."""
|
140
|
+
mcp = FastMCP("test")
|
141
|
+
|
142
|
+
# Register all components using new class-based API
|
143
|
+
documentation_component = documentation.get_component()
|
144
|
+
documentation_component.register(mcp)
|
145
|
+
|
146
|
+
codebase_component = codebase.get_component()
|
147
|
+
codebase_component.register(mcp)
|
148
|
+
|
149
|
+
tutorials_component = tutorials.get_component()
|
150
|
+
tutorials_component.register(mcp)
|
151
|
+
|
152
|
+
# Create execution component with test context
|
153
|
+
test_session_context = {}
|
154
|
+
test_object_registry = {}
|
155
|
+
execution_component = execution.create_component(
|
156
|
+
session_context=test_session_context, object_registry=test_object_registry
|
157
|
+
)
|
158
|
+
execution_component.register(mcp)
|
159
|
+
|
160
|
+
# Health component still uses legacy registration
|
161
|
+
health.register_components(mcp)
|
162
|
+
|
163
|
+
resource_paths, tool_names = collect_resources_and_tools(mcp)
|
164
|
+
|
165
|
+
# Check for duplicate resource paths
|
166
|
+
path_counts = {}
|
167
|
+
for path in resource_paths:
|
168
|
+
assert VALID_RESOURCE_PATH.match(path), f"Invalid resource path: {path}"
|
169
|
+
path_counts[path] = path_counts.get(path, 0) + 1
|
170
|
+
assert path_counts[path] == 1, f"Duplicate resource path: {path}"
|
171
|
+
|
172
|
+
# Check for duplicate tool names
|
173
|
+
name_counts = {}
|
174
|
+
for name in tool_names:
|
175
|
+
assert VALID_TOOL_NAME.match(name), f"Invalid tool name: {name}"
|
176
|
+
name_counts[name] = name_counts.get(name, 0) + 1
|
177
|
+
assert name_counts[name] == 1, f"Duplicate tool name: {name}"
|
178
|
+
|
179
|
+
|
180
|
+
def test_expected_resources_exist():
|
181
|
+
"""Test that all expected resources are registered."""
|
182
|
+
mcp = FastMCP("test")
|
183
|
+
|
184
|
+
# Register all components using new class-based API
|
185
|
+
documentation_component = documentation.get_component()
|
186
|
+
documentation_component.register(mcp)
|
187
|
+
|
188
|
+
codebase_component = codebase.get_component()
|
189
|
+
codebase_component.register(mcp)
|
190
|
+
|
191
|
+
tutorials_component = tutorials.get_component()
|
192
|
+
tutorials_component.register(mcp)
|
193
|
+
|
194
|
+
# Create execution component with test context
|
195
|
+
test_session_context = {}
|
196
|
+
test_object_registry = {}
|
197
|
+
execution_component = execution.create_component(
|
198
|
+
session_context=test_session_context, object_registry=test_object_registry
|
199
|
+
)
|
200
|
+
execution_component.register(mcp)
|
201
|
+
|
202
|
+
# Health component still uses legacy registration
|
203
|
+
health.register_components(mcp)
|
204
|
+
|
205
|
+
resource_paths, _ = collect_resources_and_tools(mcp)
|
206
|
+
|
207
|
+
# Debug: Print all registered resources
|
208
|
+
print("\nRegistered resources:")
|
209
|
+
for path in sorted(resource_paths):
|
210
|
+
print(f" {path}")
|
211
|
+
print()
|
212
|
+
|
213
|
+
# List of expected base resources (no templates)
|
214
|
+
expected_resources = {
|
215
|
+
"napistu://documentation/summary",
|
216
|
+
"napistu://codebase/summary",
|
217
|
+
"napistu://tutorials/index",
|
218
|
+
"napistu://execution/registry",
|
219
|
+
"napistu://execution/environment",
|
220
|
+
"napistu://health",
|
221
|
+
}
|
222
|
+
|
223
|
+
# Check that each expected resource exists
|
224
|
+
for resource in expected_resources:
|
225
|
+
assert resource in resource_paths, f"Missing expected resource: {resource}"
|
226
|
+
|
227
|
+
|
228
|
+
def test_expected_tools_exist():
|
229
|
+
"""Test that all expected tools are registered."""
|
230
|
+
mcp = FastMCP("test")
|
231
|
+
|
232
|
+
# Register all components using new class-based API
|
233
|
+
documentation_component = documentation.get_component()
|
234
|
+
documentation_component.register(mcp)
|
235
|
+
|
236
|
+
codebase_component = codebase.get_component()
|
237
|
+
codebase_component.register(mcp)
|
238
|
+
|
239
|
+
tutorials_component = tutorials.get_component()
|
240
|
+
tutorials_component.register(mcp)
|
241
|
+
|
242
|
+
# Create execution component with test context
|
243
|
+
test_session_context = {}
|
244
|
+
test_object_registry = {}
|
245
|
+
execution_component = execution.create_component(
|
246
|
+
session_context=test_session_context, object_registry=test_object_registry
|
247
|
+
)
|
248
|
+
execution_component.register(mcp)
|
249
|
+
|
250
|
+
# Health component still uses legacy registration
|
251
|
+
health.register_components(mcp)
|
252
|
+
|
253
|
+
_, tool_names = collect_resources_and_tools(mcp)
|
254
|
+
|
255
|
+
# Debug: Print all registered tools
|
256
|
+
print("\nRegistered tools:")
|
257
|
+
for name in sorted(tool_names):
|
258
|
+
print(f" {name}")
|
259
|
+
print()
|
260
|
+
|
261
|
+
# List of expected tools
|
262
|
+
expected_tools = {
|
263
|
+
"search_documentation",
|
264
|
+
"search_codebase",
|
265
|
+
"get_function_documentation",
|
266
|
+
"get_class_documentation",
|
267
|
+
"search_tutorials",
|
268
|
+
"list_registry",
|
269
|
+
"describe_object",
|
270
|
+
"execute_function",
|
271
|
+
"search_paths",
|
272
|
+
"check_health",
|
273
|
+
}
|
274
|
+
|
275
|
+
# Check that all expected tools exist
|
276
|
+
for tool in expected_tools:
|
277
|
+
assert tool in tool_names, f"Missing expected tool: {tool}"
|
278
|
+
|
279
|
+
|
280
|
+
def test_component_state_health():
|
281
|
+
"""Test that component states can provide health information."""
|
282
|
+
# Test documentation component state
|
283
|
+
doc_component = documentation.get_component()
|
284
|
+
doc_state = doc_component.get_state()
|
285
|
+
health_status = doc_state.get_health_status()
|
286
|
+
assert "status" in health_status
|
287
|
+
assert health_status["status"] in [
|
288
|
+
"initializing",
|
289
|
+
"healthy",
|
290
|
+
"inactive",
|
291
|
+
"unavailable",
|
292
|
+
]
|
293
|
+
|
294
|
+
# Test codebase component state
|
295
|
+
codebase_component = codebase.get_component()
|
296
|
+
codebase_state = codebase_component.get_state()
|
297
|
+
health_status = codebase_state.get_health_status()
|
298
|
+
assert "status" in health_status
|
299
|
+
assert health_status["status"] in [
|
300
|
+
"initializing",
|
301
|
+
"healthy",
|
302
|
+
"inactive",
|
303
|
+
"unavailable",
|
304
|
+
]
|
305
|
+
|
306
|
+
# Test tutorials component state
|
307
|
+
tutorials_component = tutorials.get_component()
|
308
|
+
tutorials_state = tutorials_component.get_state()
|
309
|
+
health_status = tutorials_state.get_health_status()
|
310
|
+
assert "status" in health_status
|
311
|
+
assert health_status["status"] in [
|
312
|
+
"initializing",
|
313
|
+
"healthy",
|
314
|
+
"inactive",
|
315
|
+
"unavailable",
|
316
|
+
]
|
317
|
+
|
318
|
+
# Test execution component state
|
319
|
+
test_session_context = {"test": "value"}
|
320
|
+
test_object_registry = {}
|
321
|
+
execution_component = execution.create_component(
|
322
|
+
session_context=test_session_context, object_registry=test_object_registry
|
323
|
+
)
|
324
|
+
execution_state = execution_component.get_state()
|
325
|
+
health_status = execution_state.get_health_status()
|
326
|
+
assert "status" in health_status
|
327
|
+
assert health_status["status"] in [
|
328
|
+
"initializing",
|
329
|
+
"healthy",
|
330
|
+
"inactive",
|
331
|
+
"unavailable",
|
332
|
+
]
|
333
|
+
|
334
|
+
|
335
|
+
def test_execution_object_registration():
|
336
|
+
"""Test that execution component can register and track objects."""
|
337
|
+
test_session_context = {}
|
338
|
+
test_object_registry = {}
|
339
|
+
execution_component = execution.create_component(
|
340
|
+
session_context=test_session_context, object_registry=test_object_registry
|
341
|
+
)
|
342
|
+
|
343
|
+
# Register a test object
|
344
|
+
test_object = {"test": "data"}
|
345
|
+
execution_component.register_object("test_obj", test_object)
|
346
|
+
|
347
|
+
# Check that object was registered
|
348
|
+
state = execution_component.get_state()
|
349
|
+
assert "test_obj" in state.session_objects
|
350
|
+
assert state.session_objects["test_obj"] == test_object
|
351
|
+
|
352
|
+
# Check health details include the object
|
353
|
+
health_details = state.get_health_details()
|
354
|
+
assert "registered_objects" in health_details
|
355
|
+
assert health_details["registered_objects"] == 1
|
356
|
+
assert "test_obj" in health_details["object_names"]
|