Skip to content

Commit 0b0e364

Browse files
committed
- Polish import and initialization tests
- Expand the dev section on testing in README
1 parent 0c7184c commit 0b0e364

3 files changed

Lines changed: 98 additions & 184 deletions

File tree

README.md

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,38 @@ Once the servers passes the checks, register the new MCP server:
245245
- to unified interface in `src/parse_patrol/`. **Only the central interface is exposed in `main` branch!** This allows agents and users to access all parsing tools via a single, clear server endpoint.
246246
- in this `README.md`, if this is a new tool.
247247

248-
Testing is done 2 ways:
248+
### Testing
249249

250-
- the agent attempts to generate pipeline scripts. Successful cases may be stored under in `.pipelines/scripts/`. The input to be processed is found in `.pipelines/data/`. Schema definitions and documentation resources are available in `.resources/`.
251-
- test the parser and server code in `tests/`.
250+
Testing is done in multiple ways to ensure both functionality and MCP server behavior:
251+
252+
#### 1. Automated Unit Tests
253+
Run the comprehensive test suite using pytest:
254+
255+
```bash
256+
# Run all tests with verbose output
257+
uv run python -m pytest tests/ -v
258+
259+
# Run specific test file
260+
uv run python -m pytest tests/test_modular_mcp.py -v
261+
262+
# Run tests with coverage (if coverage is installed)
263+
uv run python -m pytest tests/ --cov=parse_patrol
264+
```
265+
266+
The test suite includes:
267+
268+
- **MCP Server Initialization**: Tests that the unified MCP server loads correctly
269+
- **Parser Configuration**: Parametrized tests ensuring each parser has proper configuration
270+
- **Runtime Import Tests**: Validates that parsers work with their dependencies (gracefully skips if dependencies missing)
271+
- **Minimal Dependency Tests**: Tests core MCP functionality without optional parser dependencies
272+
273+
#### 2. Agent Pipeline Testing
274+
Test end-to-end workflows:
275+
276+
- The agent attempts to generate pipeline scripts using the MCP tools
277+
- Successful cases are stored in `.pipelines/scripts/`
278+
- Input data for processing is found in `.pipelines/data/`
279+
- Schema definitions and documentation resources are available in `.resources/`
252280

253281
## Online Resources
254282

tests/test_modular_mcp.py

Lines changed: 67 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -5,151 +5,113 @@
55
"""
66

77
import sys
8-
import pytest
9-
from unittest.mock import patch
8+
import pytest # pyright: ignore[reportMissingImports]
109

1110

12-
def test_mcp_server_with_all_dependencies():
13-
"""Test MCP server loads successfully when all dependencies are available."""
11+
# Test configurations - moved to top for easy maintenance
12+
PARSER_TEST_CONFIGS = [
13+
("cclib parser", "parse_patrol.parsers.cclib.__main__", "cclib_parse_file_to_model"),
14+
("gaussian parser", "parse_patrol.parsers.gaussian.__main__", "gauss_parse_file_to_model"),
15+
("iodata parser", "parse_patrol.parsers.iodata.__main__", "iodata_parse_file_to_model"),
16+
("NOMAD database", "parse_patrol.databases.nomad.__main__", "search_nomad_entries"),
17+
]
18+
19+
20+
def test_mcp_server_initialization():
21+
"""Test MCP server loads successfully and basic structure is correct.
22+
23+
This test validates the core MCP server setup:
24+
- MCP server object is created with correct name
25+
- Parser configuration list exists and is properly structured
26+
27+
NOTE: This tests the basic server initialization, not individual parser configs.
28+
Individual parser configs are tested separately with parametrize.
29+
"""
1430
# Add src to path for imports
1531
import os
1632
src_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'src')
1733
if src_path not in sys.path:
1834
sys.path.insert(0, src_path)
1935

20-
# Import and initialize MCP server
36+
# Import and initialize MCP server (parsers register automatically)
2137
from parse_patrol.__main__ import mcp
2238

2339
# Test that MCP server was created successfully
2440
assert mcp is not None
2541
assert mcp.name == "Parse Patrol - Unified Chemistry Parser"
26-
27-
28-
def test_mcp_server_dynamic_registration():
29-
"""Test that parsers are registered dynamically based on available dependencies."""
30-
import os
31-
src_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'src')
32-
if src_path not in sys.path:
33-
sys.path.insert(0, src_path)
34-
35-
# Capture output from registration process
36-
import io
37-
import contextlib
38-
39-
captured_output = io.StringIO()
40-
41-
# Import with captured output
42-
with contextlib.redirect_stdout(captured_output):
43-
from parse_patrol.__main__ import mcp
4442

45-
output = captured_output.getvalue()
46-
47-
# Check that registration messages were printed
48-
assert "Registered cclib parser" in output or "cclib parser not available" in output
49-
assert "Registered gaussian parser" in output or "gaussian parser not available" in output
50-
assert "Registered iodata parser" in output or "iodata parser not available" in output
51-
assert "Registered NOMAD database" in output or "NOMAD database not available" in output
43+
# Test that we can access the parser configs (shows modular design works)
44+
from parse_patrol.__main__ import parser_configs
45+
assert len(parser_configs) > 0
46+
assert isinstance(parser_configs, list)
47+
# Individual parser configs are tested in test_parser_config_exists
5248

5349

54-
def test_mcp_server_handles_missing_dependencies():
55-
"""Test that MCP server handles missing dependencies gracefully."""
50+
@pytest.mark.parametrize(
51+
"expected_parser_name",
52+
[config[0] for config in PARSER_TEST_CONFIGS]
53+
)
54+
def test_parser_config_exists(expected_parser_name):
55+
"""Test that each expected parser has a configuration entry.
56+
57+
This test validates STATIC CONFIGURATION for individual parsers:
58+
- Each parser has an entry in the parser_configs list
59+
- The configuration has the expected name
60+
61+
NOTE: This tests configuration PRESENCE, not functionality.
62+
The config can exist even if the parser's dependencies are missing.
63+
"""
5664
import os
5765
src_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'src')
5866
if src_path not in sys.path:
5967
sys.path.insert(0, src_path)
6068

61-
# Mock missing cclib dependency
62-
with patch.dict('sys.modules', {'cclib': None}):
63-
with patch('builtins.__import__', side_effect=lambda name, *args, **kwargs:
64-
__import__(name, *args, **kwargs) if name != 'cclib'
65-
else (_ for _ in ()).throw(ImportError(f"No module named '{name}'"))):
66-
67-
# This should work even with cclib missing
68-
from mcp.server.fastmcp import FastMCP
69-
test_mcp = FastMCP("Test Parse Patrol")
70-
71-
# Test that we can create MCP server without all dependencies
72-
assert test_mcp is not None
69+
from parse_patrol.__main__ import parser_configs
70+
assert any(config["name"] == expected_parser_name for config in parser_configs), \
71+
f"Parser config for '{expected_parser_name}' not found"
7372

7473

75-
def test_individual_parser_imports():
76-
"""Test importing individual parsers to ensure they work independently."""
74+
@pytest.mark.parametrize("parser_name,module_path,function_name", PARSER_TEST_CONFIGS)
75+
def test_individual_parser_imports(parser_name, module_path, function_name):
76+
"""Test importing individual parsers to ensure they work at RUNTIME.
77+
78+
This test validates ACTUAL FUNCTIONALITY for each parser:
79+
- Parser module can be imported successfully
80+
- Required dependencies are available (cclib, iodata, etc.)
81+
- Expected functions exist and are callable
82+
83+
NOTE: This tests RUNTIME functionality, not just configuration.
84+
This test will fail/skip if dependencies are missing, even if
85+
the parser configuration exists and is correct.
86+
87+
Contrast with test_parser_config_exists which only tests static config.
88+
"""
7789
import os
7890
src_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'src')
7991
if src_path not in sys.path:
8092
sys.path.insert(0, src_path)
8193

82-
# Test cclib parser
8394
try:
84-
from parse_patrol.parsers.cclib.__main__ import cclib_parse_file_to_model
85-
assert callable(cclib_parse_file_to_model)
86-
print("✓ cclib parser imports successfully")
95+
module = __import__(module_path, fromlist=[function_name])
96+
func = getattr(module, function_name)
97+
assert callable(func)
98+
print(f"✓ {parser_name} imports successfully")
8799
except ImportError as e:
88-
print(f"⚠ cclib parser not available: {e}")
89-
90-
# Test gaussian parser
91-
try:
92-
from parse_patrol.parsers.gaussian.__main__ import gauss_parse_file_to_model
93-
assert callable(gauss_parse_file_to_model)
94-
print("✓ gaussian parser imports successfully")
95-
except ImportError as e:
96-
print(f"⚠ gaussian parser not available: {e}")
97-
98-
# Test iodata parser
99-
try:
100-
from parse_patrol.parsers.iodata.__main__ import iodata_parse_file_to_model
101-
assert callable(iodata_parse_file_to_model)
102-
print("✓ iodata parser imports successfully")
103-
except ImportError as e:
104-
print(f"⚠ iodata parser not available: {e}")
105-
106-
# Test NOMAD database
107-
try:
108-
from parse_patrol.databases.nomad.__main__ import search_nomad_entries
109-
assert callable(search_nomad_entries)
110-
print("✓ NOMAD database imports successfully")
111-
except ImportError as e:
112-
print(f"⚠ NOMAD database not available: {e}")
100+
print(f"⚠ {parser_name} not available: {e}")
101+
pytest.skip(f"Skipping {parser_name} test due to missing dependencies")
113102

114103

115104
def test_mcp_server_without_optional_deps():
116-
"""Test MCP server with minimal dependencies (only MCP core)."""
105+
"""Test MCP server core functionality without parser dependencies."""
117106
import os
118107
src_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'src')
119108
if src_path not in sys.path:
120109
sys.path.insert(0, src_path)
121110

122111
# Test that we can at least import the MCP framework
123-
from mcp.server.fastmcp import FastMCP
112+
from mcp.server.fastmcp import FastMCP # pyright: ignore[reportMissingImports]
124113

125114
# Create a minimal MCP server
126115
minimal_mcp = FastMCP("Minimal Parse Patrol")
127116
assert minimal_mcp is not None
128-
assert minimal_mcp.name == "Minimal Parse Patrol"
129-
130-
131-
if __name__ == "__main__":
132-
# Run tests directly if script is executed
133-
print("=== Testing Modular MCP Server ===\n")
134-
135-
print("1. Testing MCP server with all dependencies...")
136-
test_mcp_server_with_all_dependencies()
137-
print("✓ Passed\n")
138-
139-
print("2. Testing dynamic registration...")
140-
test_mcp_server_dynamic_registration()
141-
print("✓ Passed\n")
142-
143-
print("3. Testing individual parser imports...")
144-
test_individual_parser_imports()
145-
print("✓ Passed\n")
146-
147-
print("4. Testing MCP server with minimal dependencies...")
148-
test_mcp_server_without_optional_deps()
149-
print("✓ Passed\n")
150-
151-
print("5. Testing graceful handling of missing dependencies...")
152-
test_mcp_server_handles_missing_dependencies()
153-
print("✓ Passed\n")
154-
155-
print("🎉 All tests passed!")
117+
assert minimal_mcp.name == "Minimal Parse Patrol"

tests/test_modular_mcp_old.py

Lines changed: 0 additions & 76 deletions
This file was deleted.

0 commit comments

Comments
 (0)