55"""
66
77import 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
115104def 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"
0 commit comments