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.
Files changed (107) hide show
  1. napistu/__main__.py +126 -96
  2. napistu/constants.py +35 -41
  3. napistu/context/__init__.py +10 -0
  4. napistu/context/discretize.py +462 -0
  5. napistu/context/filtering.py +387 -0
  6. napistu/gcs/__init__.py +1 -1
  7. napistu/identifiers.py +74 -15
  8. napistu/indices.py +68 -0
  9. napistu/ingestion/__init__.py +1 -1
  10. napistu/ingestion/bigg.py +47 -62
  11. napistu/ingestion/constants.py +18 -133
  12. napistu/ingestion/gtex.py +113 -0
  13. napistu/ingestion/hpa.py +147 -0
  14. napistu/ingestion/sbml.py +0 -97
  15. napistu/ingestion/string.py +2 -2
  16. napistu/matching/__init__.py +10 -0
  17. napistu/matching/constants.py +18 -0
  18. napistu/matching/interactions.py +518 -0
  19. napistu/matching/mount.py +529 -0
  20. napistu/matching/species.py +510 -0
  21. napistu/mcp/__init__.py +7 -4
  22. napistu/mcp/__main__.py +128 -72
  23. napistu/mcp/client.py +16 -25
  24. napistu/mcp/codebase.py +201 -153
  25. napistu/mcp/component_base.py +170 -0
  26. napistu/mcp/config.py +223 -0
  27. napistu/mcp/constants.py +45 -2
  28. napistu/mcp/documentation.py +253 -136
  29. napistu/mcp/documentation_utils.py +13 -48
  30. napistu/mcp/execution.py +372 -305
  31. napistu/mcp/health.py +49 -67
  32. napistu/mcp/profiles.py +10 -6
  33. napistu/mcp/server.py +161 -80
  34. napistu/mcp/tutorials.py +139 -87
  35. napistu/modify/__init__.py +1 -1
  36. napistu/modify/gaps.py +1 -1
  37. napistu/network/__init__.py +1 -1
  38. napistu/network/constants.py +101 -34
  39. napistu/network/data_handling.py +388 -0
  40. napistu/network/ig_utils.py +351 -0
  41. napistu/network/napistu_graph_core.py +354 -0
  42. napistu/network/neighborhoods.py +40 -40
  43. napistu/network/net_create.py +373 -309
  44. napistu/network/net_propagation.py +47 -19
  45. napistu/network/{net_utils.py → ng_utils.py} +124 -272
  46. napistu/network/paths.py +67 -51
  47. napistu/network/precompute.py +11 -11
  48. napistu/ontologies/__init__.py +10 -0
  49. napistu/ontologies/constants.py +129 -0
  50. napistu/ontologies/dogma.py +243 -0
  51. napistu/ontologies/genodexito.py +649 -0
  52. napistu/ontologies/mygene.py +369 -0
  53. napistu/ontologies/renaming.py +198 -0
  54. napistu/rpy2/__init__.py +229 -86
  55. napistu/rpy2/callr.py +47 -77
  56. napistu/rpy2/constants.py +24 -23
  57. napistu/rpy2/rids.py +61 -648
  58. napistu/sbml_dfs_core.py +587 -222
  59. napistu/scverse/__init__.py +15 -0
  60. napistu/scverse/constants.py +28 -0
  61. napistu/scverse/loading.py +727 -0
  62. napistu/utils.py +118 -10
  63. {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/METADATA +8 -3
  64. napistu-0.3.1.dist-info/RECORD +133 -0
  65. tests/conftest.py +22 -0
  66. tests/test_context_discretize.py +56 -0
  67. tests/test_context_filtering.py +267 -0
  68. tests/test_identifiers.py +100 -0
  69. tests/test_indices.py +65 -0
  70. tests/{test_edgelist.py → test_ingestion_napistu_edgelist.py} +2 -2
  71. tests/test_matching_interactions.py +108 -0
  72. tests/test_matching_mount.py +305 -0
  73. tests/test_matching_species.py +394 -0
  74. tests/test_mcp_config.py +193 -0
  75. tests/test_mcp_documentation_utils.py +12 -3
  76. tests/test_mcp_server.py +356 -0
  77. tests/test_network_data_handling.py +397 -0
  78. tests/test_network_ig_utils.py +23 -0
  79. tests/test_network_neighborhoods.py +19 -0
  80. tests/test_network_net_create.py +459 -0
  81. tests/test_network_ng_utils.py +30 -0
  82. tests/test_network_paths.py +56 -0
  83. tests/{test_precomputed_distances.py → test_network_precompute.py} +8 -6
  84. tests/test_ontologies_genodexito.py +58 -0
  85. tests/test_ontologies_mygene.py +39 -0
  86. tests/test_ontologies_renaming.py +110 -0
  87. tests/test_rpy2_callr.py +79 -0
  88. tests/test_rpy2_init.py +151 -0
  89. tests/test_sbml.py +0 -31
  90. tests/test_sbml_dfs_core.py +134 -10
  91. tests/test_scverse_loading.py +778 -0
  92. tests/test_set_coverage.py +2 -2
  93. tests/test_utils.py +121 -1
  94. napistu/mechanism_matching.py +0 -1353
  95. napistu/rpy2/netcontextr.py +0 -467
  96. napistu-0.2.5.dev6.dist-info/RECORD +0 -97
  97. tests/test_igraph.py +0 -367
  98. tests/test_mechanism_matching.py +0 -784
  99. tests/test_net_utils.py +0 -149
  100. tests/test_netcontextr.py +0 -105
  101. tests/test_rpy2.py +0 -61
  102. /napistu/ingestion/{cpr_edgelist.py → napistu_edgelist.py} +0 -0
  103. {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/WHEEL +0 -0
  104. {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/entry_points.txt +0 -0
  105. {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/licenses/LICENSE +0 -0
  106. {napistu-0.2.5.dev6.dist-info → napistu-0.3.1.dist-info}/top_level.txt +0 -0
  107. /tests/{test_obo.py → test_ingestion_obo.py} +0 -0
@@ -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
@@ -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"]