Files
Atomizer/tests/mcp_server/tools/test_model_discovery.py

212 lines
6.9 KiB
Python
Raw Normal View History

feat: Implement Option A - MCP Model Discovery tool This commit implements the first phase of the MCP server as outlined in PROJECT_SUMMARY.md Option A: Model Discovery. New Features: - Complete .sim file parser (XML-based) - Expression extraction from .sim and .prt files - Solution, FEM, materials, loads, constraints extraction - Structured JSON output for LLM consumption - Markdown formatting for human-readable output Implementation Details: - mcp_server/tools/model_discovery.py: Core parser and discovery logic - SimFileParser class: Handles XML parsing of .sim files - discover_fea_model(): Main MCP tool function - format_discovery_result_for_llm(): Markdown formatter - mcp_server/tools/__init__.py: Updated to export new functions - mcp_server/tools/README.md: Complete documentation for MCP tools Testing & Examples: - examples/test_bracket.sim: Sample .sim file for testing - tests/mcp_server/tools/test_model_discovery.py: Comprehensive unit tests - Manual testing verified: Successfully extracts 4 expressions, solution info, mesh data, materials, loads, and constraints Validation: - Command-line tool works: python mcp_server/tools/model_discovery.py examples/test_bracket.sim - Output includes both Markdown and JSON formats - Error handling for missing files and invalid formats Next Steps (Phase 2): - Port optimization engine from P04 Atomizer - Implement build_optimization_config tool - Create pluggable result extractor system References: - PROJECT_SUMMARY.md: Option A (lines 339-350) - mcp_server/prompts/system_prompt.md: Model Discovery workflow
2025-11-15 13:23:05 +00:00
"""
Unit tests for MCP Model Discovery Tool
Tests the .sim file parser and FEA model discovery functionality.
"""
import pytest
from pathlib import Path
import sys
# Add project root to path
project_root = Path(__file__).parent.parent.parent.parent
sys.path.insert(0, str(project_root))
from mcp_server.tools.model_discovery import (
discover_fea_model,
format_discovery_result_for_llm,
SimFileParser
)
class TestSimFileParser:
"""Test the SimFileParser class"""
@pytest.fixture
def example_sim_path(self):
"""Path to example .sim file"""
return project_root / "examples" / "test_bracket.sim"
def test_parser_initialization(self, example_sim_path):
"""Test that parser initializes correctly"""
parser = SimFileParser(example_sim_path)
assert parser.sim_path.exists()
assert parser.tree is not None
assert parser.root is not None
def test_parser_file_not_found(self):
"""Test error handling for missing file"""
with pytest.raises(FileNotFoundError):
SimFileParser("/nonexistent/path/file.sim")
def test_parser_invalid_extension(self):
"""Test error handling for non-.sim file"""
with pytest.raises(ValueError):
SimFileParser(project_root / "README.md")
def test_extract_solutions(self, example_sim_path):
"""Test solution extraction"""
parser = SimFileParser(example_sim_path)
solutions = parser.extract_solutions()
assert len(solutions) > 0
assert solutions[0]['name'] == 'Structural Analysis 1'
assert solutions[0]['type'] == 'Static Structural'
assert solutions[0]['solver'] == 'NX Nastran'
def test_extract_expressions(self, example_sim_path):
"""Test expression extraction"""
parser = SimFileParser(example_sim_path)
expressions = parser.extract_expressions()
assert len(expressions) > 0
# Check for expected expressions
expr_names = [e['name'] for e in expressions]
assert 'wall_thickness' in expr_names
assert 'hole_diameter' in expr_names
assert 'rib_spacing' in expr_names
# Check expression values
wall_thickness = next(e for e in expressions if e['name'] == 'wall_thickness')
assert wall_thickness['value'] == '5.0'
assert wall_thickness['units'] == 'mm'
def test_extract_fem_info(self, example_sim_path):
"""Test FEM information extraction"""
parser = SimFileParser(example_sim_path)
fem_info = parser.extract_fem_info()
# Check mesh info
assert 'mesh' in fem_info
assert fem_info['mesh']['name'] == 'Bracket Mesh'
assert fem_info['mesh']['node_count'] == '8234'
assert fem_info['mesh']['element_count'] == '4521'
# Check materials
assert len(fem_info['materials']) > 0
assert fem_info['materials'][0]['name'] == 'Aluminum 6061-T6'
# Check loads
assert len(fem_info['loads']) > 0
assert fem_info['loads'][0]['name'] == 'Applied Force'
# Check constraints
assert len(fem_info['constraints']) > 0
assert fem_info['constraints'][0]['name'] == 'Fixed Support'
class TestDiscoverFEAModel:
"""Test the main discover_fea_model function"""
@pytest.fixture
def example_sim_path(self):
"""Path to example .sim file"""
return str(project_root / "examples" / "test_bracket.sim")
def test_successful_discovery(self, example_sim_path):
"""Test successful model discovery"""
result = discover_fea_model(example_sim_path)
assert result['status'] == 'success'
assert result['file_exists'] is True
assert 'solutions' in result
assert 'expressions' in result
assert 'fem_info' in result
assert 'summary' in result
# Check summary statistics
assert result['summary']['solution_count'] >= 1
assert result['summary']['expression_count'] >= 3
def test_file_not_found_error(self):
"""Test error handling for missing file"""
result = discover_fea_model("/nonexistent/file.sim")
assert result['status'] == 'error'
assert result['error_type'] == 'file_not_found'
assert 'message' in result
assert 'suggestion' in result
def test_result_structure(self, example_sim_path):
"""Test that result has expected structure"""
result = discover_fea_model(example_sim_path)
# Check top-level keys
expected_keys = ['status', 'sim_file', 'file_exists', 'solutions',
'expressions', 'fem_info', 'linked_files', 'metadata', 'summary']
for key in expected_keys:
assert key in result, f"Missing key: {key}"
# Check summary keys
expected_summary_keys = ['solution_count', 'expression_count',
'material_count', 'load_count', 'constraint_count']
for key in expected_summary_keys:
assert key in result['summary'], f"Missing summary key: {key}"
class TestFormatDiscoveryResult:
"""Test the Markdown formatting function"""
@pytest.fixture
def example_sim_path(self):
"""Path to example .sim file"""
return str(project_root / "examples" / "test_bracket.sim")
def test_format_success_result(self, example_sim_path):
"""Test formatting of successful discovery"""
result = discover_fea_model(example_sim_path)
formatted = format_discovery_result_for_llm(result)
assert isinstance(formatted, str)
assert '# FEA Model Analysis' in formatted
assert 'Solutions' in formatted
assert 'Expressions' in formatted
assert 'wall_thickness' in formatted
def test_format_error_result(self):
"""Test formatting of error result"""
result = discover_fea_model("/nonexistent/file.sim")
formatted = format_discovery_result_for_llm(result)
assert isinstance(formatted, str)
assert '' in formatted or 'Error' in formatted
assert result['message'] in formatted
# Integration test
def test_end_to_end_workflow():
"""
Test the complete workflow:
1. Discover model
2. Format for LLM
3. Verify output is useful
"""
example_sim = str(project_root / "examples" / "test_bracket.sim")
# Step 1: Discover
result = discover_fea_model(example_sim)
assert result['status'] == 'success'
# Step 2: Format
formatted = format_discovery_result_for_llm(result)
assert len(formatted) > 100 # Should be substantial output
# Step 3: Verify key information is present
assert 'wall_thickness' in formatted
assert 'Aluminum' in formatted
assert 'Static Structural' in formatted
print("\n" + "="*60)
print("INTEGRATION TEST OUTPUT:")
print("="*60)
print(formatted)
print("="*60)
if __name__ == "__main__":
# Run tests with pytest
pytest.main([__file__, "-v", "-s"])