Files
freecad-robust-mcp-fc111/tests/unit/addon/test_addon_structure.py
T
Sean P. KaneandGitHub f14ab8177d Fix: rename workbench and prepare for release (#27)
* chore: rename workbench to FreecadRobustMCPBridge

* fix: stdio cleanup and bug fix for JSON RPC

* fix: update FreeCAD MCP bridge and tests

* test: Fix GUI Integration tests and other issues

* fix: unit tests in CI and a few other things

* docs: generate GitHub pages site and link to it.

* fix: General code improvements and DRY refactoring

* chore: General cleanup

* chore: small fixes

* docs: cleanup

* docs: fixes

* docs: small corrections

* docs: fix

* test: release check

* bump: workbench v0.6.0

* chore: bump macros to 0.6.0

* docs: Release improvements

* refactor: improve DRYness of code
2026-01-12 09:30:05 -08:00

207 lines
8.4 KiB
Python

"""Tests for the FreeCAD Robust MCP workbench addon structure.
These tests verify that the addon has the correct file structure and
that the Python files are valid (can be parsed).
"""
import ast
from pathlib import Path
import pytest
# Get the addon directory path
ADDON_DIR = (
Path(__file__).parent.parent.parent.parent / "addon" / "FreecadRobustMCPBridge"
)
class TestAddonFileStructure:
"""Tests for addon file structure."""
def test_addon_directory_exists(self) -> None:
"""The addon directory should exist."""
assert ADDON_DIR.exists(), f"Addon directory not found: {ADDON_DIR}"
assert ADDON_DIR.is_dir(), f"Addon path is not a directory: {ADDON_DIR}"
def test_init_py_exists(self) -> None:
"""Init.py should exist in the addon directory."""
init_file = ADDON_DIR / "Init.py"
assert init_file.exists(), f"Init.py not found: {init_file}"
def test_initgui_py_exists(self) -> None:
"""InitGui.py should exist in the addon directory."""
initgui_file = ADDON_DIR / "InitGui.py"
assert initgui_file.exists(), f"InitGui.py not found: {initgui_file}"
def test_icon_exists(self) -> None:
"""The workbench icon should exist."""
icon_file = ADDON_DIR / "FreecadRobustMCPBridge.svg"
assert icon_file.exists(), f"Icon not found: {icon_file}"
def test_bridge_module_exists(self) -> None:
"""The bridge module directory should exist."""
bridge_dir = ADDON_DIR / "freecad_mcp_bridge"
assert bridge_dir.exists(), f"Bridge module not found: {bridge_dir}"
assert bridge_dir.is_dir(), f"Bridge path is not a directory: {bridge_dir}"
def test_bridge_init_exists(self) -> None:
"""The bridge module __init__.py should exist."""
init_file = ADDON_DIR / "freecad_mcp_bridge" / "__init__.py"
assert init_file.exists(), f"Bridge __init__.py not found: {init_file}"
def test_bridge_server_exists(self) -> None:
"""The bridge server.py should exist."""
server_file = ADDON_DIR / "freecad_mcp_bridge" / "server.py"
assert server_file.exists(), f"Bridge server.py not found: {server_file}"
def test_blocking_bridge_exists(self) -> None:
"""The blocking_bridge.py should exist for blocking server mode."""
blocking_file = ADDON_DIR / "freecad_mcp_bridge" / "blocking_bridge.py"
assert blocking_file.exists(), f"blocking_bridge.py not found: {blocking_file}"
def test_bridge_utils_exists(self) -> None:
"""The bridge_utils.py should exist for shared utilities."""
utils_file = ADDON_DIR / "freecad_mcp_bridge" / "bridge_utils.py"
assert utils_file.exists(), f"bridge_utils.py not found: {utils_file}"
class TestAddonPythonSyntax:
"""Tests to verify Python files have valid syntax."""
def test_init_py_valid_syntax(self) -> None:
"""Init.py should have valid Python syntax."""
init_file = ADDON_DIR / "Init.py"
code = init_file.read_text()
# This will raise SyntaxError if invalid
ast.parse(code)
def test_initgui_py_valid_syntax(self) -> None:
"""InitGui.py should have valid Python syntax."""
initgui_file = ADDON_DIR / "InitGui.py"
code = initgui_file.read_text()
# This will raise SyntaxError if invalid
ast.parse(code)
def test_bridge_init_valid_syntax(self) -> None:
"""Bridge __init__.py should have valid Python syntax."""
init_file = ADDON_DIR / "freecad_mcp_bridge" / "__init__.py"
code = init_file.read_text()
ast.parse(code)
def test_bridge_server_valid_syntax(self) -> None:
"""Bridge server.py should have valid Python syntax."""
server_file = ADDON_DIR / "freecad_mcp_bridge" / "server.py"
code = server_file.read_text()
ast.parse(code)
def test_blocking_bridge_valid_syntax(self) -> None:
"""blocking_bridge.py should have valid Python syntax."""
blocking_file = ADDON_DIR / "freecad_mcp_bridge" / "blocking_bridge.py"
code = blocking_file.read_text()
ast.parse(code)
def test_bridge_utils_valid_syntax(self) -> None:
"""bridge_utils.py should have valid Python syntax."""
utils_file = ADDON_DIR / "freecad_mcp_bridge" / "bridge_utils.py"
code = utils_file.read_text()
ast.parse(code)
class TestAddonMetadata:
"""Tests for addon metadata and content."""
def test_init_py_has_freecad_import(self) -> None:
"""Init.py should import FreeCAD."""
init_file = ADDON_DIR / "Init.py"
code = init_file.read_text()
assert "import FreeCAD" in code
def test_initgui_py_has_workbench_class(self) -> None:
"""InitGui.py should define the workbench class."""
initgui_file = ADDON_DIR / "InitGui.py"
code = initgui_file.read_text()
assert "FreecadRobustMCPBridgeWorkbench" in code
assert "Gui.Workbench" in code or "Workbench" in code
def test_initgui_py_has_commands(self) -> None:
"""InitGui.py should define start/stop commands."""
initgui_file = ADDON_DIR / "InitGui.py"
code = initgui_file.read_text()
assert "StartMCPBridgeCommand" in code
assert "StopMCPBridgeCommand" in code
def test_initgui_py_registers_workbench(self) -> None:
"""InitGui.py should register the workbench."""
initgui_file = ADDON_DIR / "InitGui.py"
code = initgui_file.read_text()
assert "Gui.addWorkbench" in code
def test_bridge_server_has_plugin_class(self) -> None:
"""Bridge server.py should have FreecadMCPPlugin class."""
server_file = ADDON_DIR / "freecad_mcp_bridge" / "server.py"
code = server_file.read_text()
assert "class FreecadMCPPlugin" in code
def test_blocking_bridge_imports_plugin(self) -> None:
"""blocking_bridge.py should import FreecadMCPPlugin."""
blocking_file = ADDON_DIR / "freecad_mcp_bridge" / "blocking_bridge.py"
code = blocking_file.read_text()
assert "FreecadMCPPlugin" in code
def test_blocking_bridge_has_run_forever(self) -> None:
"""blocking_bridge.py should call run_forever for blocking execution."""
blocking_file = ADDON_DIR / "freecad_mcp_bridge" / "blocking_bridge.py"
code = blocking_file.read_text()
assert "run_forever" in code
def test_bridge_utils_has_get_running_plugin(self) -> None:
"""bridge_utils.py should have get_running_plugin function."""
utils_file = ADDON_DIR / "freecad_mcp_bridge" / "bridge_utils.py"
code = utils_file.read_text()
assert "def get_running_plugin" in code
def test_icon_is_valid_svg(self) -> None:
"""The icon should be a valid SVG file."""
icon_file = ADDON_DIR / "FreecadRobustMCPBridge.svg"
content = icon_file.read_text()
assert content.startswith("<?xml") or content.startswith("<svg")
assert "<svg" in content
assert "</svg>" in content
class TestAddonIconSize:
"""Tests for addon icon size requirements."""
def test_icon_size_under_10kb(self) -> None:
"""The icon file should be under 10KB (FreeCAD requirement)."""
icon_file = ADDON_DIR / "FreecadRobustMCPBridge.svg"
size_bytes = icon_file.stat().st_size
size_kb = size_bytes / 1024
assert size_kb <= 10, f"Icon is {size_kb:.2f}KB, must be <= 10KB"
class TestPackageXml:
"""Tests for package.xml workbench entry."""
@pytest.fixture
def package_xml(self) -> str:
"""Load package.xml content."""
package_file = ADDON_DIR.parent.parent / "package.xml"
return package_file.read_text()
def test_workbench_entry_exists(self, package_xml: str) -> None:
"""package.xml should have a workbench entry."""
assert "<workbench>" in package_xml
def test_workbench_classname(self, package_xml: str) -> None:
"""package.xml should reference the correct workbench classname."""
assert "<classname>FreecadRobustMCPBridgeWorkbench</classname>" in package_xml
def test_workbench_subdirectory(self, package_xml: str) -> None:
"""package.xml should reference the correct subdirectory."""
assert "./addon/FreecadRobustMCPBridge/" in package_xml
def test_workbench_icon(self, package_xml: str) -> None:
"""package.xml should reference the workbench icon."""
assert "<icon>FreecadRobustMCPBridge.svg</icon>" in package_xml