Refactor (#37)
* refactor: remove macros * refactor: additional cleanup * chore: small cleanup * refactor: update .coderabbit.yaml configuration * docs: update CLAUDE.md with new documentation * docs: table alignment
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,396 +0,0 @@
|
||||
"""Integration tests for the MultiExport macro.
|
||||
|
||||
These tests verify the MultiExporter class functionality including:
|
||||
- Exporting to STL format
|
||||
- Exporting to STEP format
|
||||
- Exporting to multiple formats simultaneously
|
||||
- Handling mesh tolerance settings
|
||||
|
||||
Note: These tests require a running FreeCAD Robust MCP Bridge.
|
||||
Start it with: just freecad::run-gui or just freecad::run-headless
|
||||
|
||||
To run these tests:
|
||||
pytest tests/integration/test_multi_export.py -v
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import pytest
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import xmlrpc.client
|
||||
|
||||
# Mark all tests in this module as integration tests
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
|
||||
def execute_code(proxy: xmlrpc.client.ServerProxy, code: str) -> dict[str, Any]:
|
||||
"""Execute Python code via the MCP bridge and return the result."""
|
||||
result = proxy.execute(code) # type: ignore[union-attr]
|
||||
assert isinstance(result, dict), f"Unexpected result type: {type(result)}"
|
||||
assert result.get("success"), f"Execution failed: {result.get('error_traceback')}"
|
||||
return result
|
||||
|
||||
|
||||
# The MultiExporter class code embedded for testing
|
||||
MULTI_EXPORTER_CODE = '''
|
||||
import os
|
||||
import FreeCAD as App
|
||||
import Part
|
||||
import Mesh
|
||||
|
||||
|
||||
class MultiExporter:
|
||||
"""Handles exporting objects to multiple formats."""
|
||||
|
||||
def __init__(self, objects, params):
|
||||
"""Initialize the exporter."""
|
||||
self.objects = objects
|
||||
self.params = params
|
||||
self.exported_files = []
|
||||
self.errors = []
|
||||
|
||||
def export_all(self):
|
||||
"""Export objects to all selected formats."""
|
||||
formats = self.params["formats"]
|
||||
|
||||
if not formats:
|
||||
return [], ["No formats selected"]
|
||||
|
||||
for fmt in formats:
|
||||
try:
|
||||
filepath = self._export_format(fmt)
|
||||
self.exported_files.append(filepath)
|
||||
except Exception as e:
|
||||
self.errors.append(f"Failed to export {fmt.upper()}: {str(e)}")
|
||||
|
||||
return self.exported_files, self.errors
|
||||
|
||||
def _export_format(self, extension):
|
||||
"""Export to a specific format."""
|
||||
directory = self.params["directory"]
|
||||
base_name = self.params["base_filename"]
|
||||
filepath = os.path.join(directory, f"{base_name}.{extension}")
|
||||
|
||||
shapes = []
|
||||
for obj in self.objects:
|
||||
if hasattr(obj, "Shape"):
|
||||
shapes.append(obj.Shape)
|
||||
|
||||
if not shapes:
|
||||
raise ValueError("No exportable shapes found")
|
||||
|
||||
if len(shapes) == 1:
|
||||
combined_shape = shapes[0]
|
||||
else:
|
||||
combined_shape = Part.makeCompound(shapes)
|
||||
|
||||
if extension == "stl":
|
||||
self._export_mesh(combined_shape, filepath)
|
||||
elif extension == "step":
|
||||
combined_shape.exportStep(filepath)
|
||||
elif extension == "brep":
|
||||
combined_shape.exportBrep(filepath)
|
||||
else:
|
||||
raise ValueError(f"Unsupported format: {extension}")
|
||||
|
||||
return filepath
|
||||
|
||||
def _export_mesh(self, shape, filepath):
|
||||
"""Export shape as mesh format."""
|
||||
tolerance = self.params.get("mesh_tolerance", 0.1)
|
||||
mesh = Mesh.Mesh()
|
||||
vertices, facets = shape.tessellate(tolerance)
|
||||
mesh_data = []
|
||||
for facet in facets:
|
||||
triangle = [vertices[facet[0]], vertices[facet[1]], vertices[facet[2]]]
|
||||
mesh_data.append(triangle)
|
||||
mesh.addFacets(mesh_data)
|
||||
mesh.write(filepath)
|
||||
'''
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_export_dir():
|
||||
"""Create a temporary directory for export tests."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
yield tmpdir
|
||||
|
||||
|
||||
class TestMultiExporter:
|
||||
"""Tests for the MultiExporter functionality."""
|
||||
|
||||
def test_export_stl(self, xmlrpc_proxy, temp_export_dir):
|
||||
"""Test exporting a simple box to STL format."""
|
||||
code = f"""
|
||||
{MULTI_EXPORTER_CODE}
|
||||
|
||||
# Create a new document and simple box
|
||||
doc = App.newDocument("TestExportSTL")
|
||||
box = doc.addObject("Part::Box", "TestBox")
|
||||
box.Length = 20
|
||||
box.Width = 20
|
||||
box.Height = 10
|
||||
doc.recompute()
|
||||
|
||||
# Set up export parameters
|
||||
params = {{
|
||||
"directory": "{temp_export_dir}",
|
||||
"base_filename": "test_box",
|
||||
"formats": ["stl"],
|
||||
"mesh_tolerance": 0.1,
|
||||
}}
|
||||
|
||||
# Create exporter and export
|
||||
exporter = MultiExporter([box], params)
|
||||
exported_files, errors = exporter.export_all()
|
||||
|
||||
# Clean up
|
||||
App.closeDocument("TestExportSTL")
|
||||
|
||||
_result_ = {{
|
||||
"exported_files": exported_files,
|
||||
"errors": errors,
|
||||
"file_exists": os.path.exists(exported_files[0]) if exported_files else False,
|
||||
}}
|
||||
"""
|
||||
result = execute_code(xmlrpc_proxy, code)
|
||||
data = result.get("result", {})
|
||||
|
||||
assert len(data["exported_files"]) == 1
|
||||
assert len(data["errors"]) == 0
|
||||
assert data["file_exists"] is True
|
||||
assert data["exported_files"][0].endswith(".stl")
|
||||
|
||||
def test_export_step(self, xmlrpc_proxy, temp_export_dir):
|
||||
"""Test exporting a simple cylinder to STEP format."""
|
||||
code = f"""
|
||||
{MULTI_EXPORTER_CODE}
|
||||
|
||||
# Create a new document and simple cylinder
|
||||
doc = App.newDocument("TestExportSTEP")
|
||||
cylinder = doc.addObject("Part::Cylinder", "TestCylinder")
|
||||
cylinder.Radius = 10
|
||||
cylinder.Height = 30
|
||||
doc.recompute()
|
||||
|
||||
# Set up export parameters
|
||||
params = {{
|
||||
"directory": "{temp_export_dir}",
|
||||
"base_filename": "test_cylinder",
|
||||
"formats": ["step"],
|
||||
"mesh_tolerance": 0.1,
|
||||
}}
|
||||
|
||||
# Create exporter and export
|
||||
exporter = MultiExporter([cylinder], params)
|
||||
exported_files, errors = exporter.export_all()
|
||||
|
||||
# Clean up
|
||||
App.closeDocument("TestExportSTEP")
|
||||
|
||||
_result_ = {{
|
||||
"exported_files": exported_files,
|
||||
"errors": errors,
|
||||
"file_exists": os.path.exists(exported_files[0]) if exported_files else False,
|
||||
}}
|
||||
"""
|
||||
result = execute_code(xmlrpc_proxy, code)
|
||||
data = result.get("result", {})
|
||||
|
||||
assert len(data["exported_files"]) == 1
|
||||
assert len(data["errors"]) == 0
|
||||
assert data["file_exists"] is True
|
||||
assert data["exported_files"][0].endswith(".step")
|
||||
|
||||
def test_export_multiple_formats(self, xmlrpc_proxy, temp_export_dir):
|
||||
"""Test exporting to multiple formats simultaneously."""
|
||||
code = f"""
|
||||
{MULTI_EXPORTER_CODE}
|
||||
|
||||
# Create a new document and simple sphere
|
||||
doc = App.newDocument("TestExportMulti")
|
||||
sphere = doc.addObject("Part::Sphere", "TestSphere")
|
||||
sphere.Radius = 15
|
||||
doc.recompute()
|
||||
|
||||
# Set up export parameters for multiple formats
|
||||
params = {{
|
||||
"directory": "{temp_export_dir}",
|
||||
"base_filename": "test_sphere",
|
||||
"formats": ["stl", "step", "brep"],
|
||||
"mesh_tolerance": 0.1,
|
||||
}}
|
||||
|
||||
# Create exporter and export
|
||||
exporter = MultiExporter([sphere], params)
|
||||
exported_files, errors = exporter.export_all()
|
||||
|
||||
# Check which files exist
|
||||
files_exist = [os.path.exists(f) for f in exported_files]
|
||||
|
||||
# Clean up
|
||||
App.closeDocument("TestExportMulti")
|
||||
|
||||
_result_ = {{
|
||||
"exported_files": exported_files,
|
||||
"errors": errors,
|
||||
"files_exist": files_exist,
|
||||
"count": len(exported_files),
|
||||
}}
|
||||
"""
|
||||
result = execute_code(xmlrpc_proxy, code)
|
||||
data = result.get("result", {})
|
||||
|
||||
assert data["count"] == 3
|
||||
assert len(data["errors"]) == 0
|
||||
assert all(data["files_exist"])
|
||||
# Verify each format is present
|
||||
extensions = [os.path.splitext(f)[1] for f in data["exported_files"]]
|
||||
assert ".stl" in extensions
|
||||
assert ".step" in extensions
|
||||
assert ".brep" in extensions
|
||||
|
||||
def test_export_multiple_objects(self, xmlrpc_proxy, temp_export_dir):
|
||||
"""Test exporting multiple objects as a compound."""
|
||||
code = f"""
|
||||
{MULTI_EXPORTER_CODE}
|
||||
|
||||
# Create a new document with multiple objects
|
||||
doc = App.newDocument("TestExportMultiObj")
|
||||
box = doc.addObject("Part::Box", "Box1")
|
||||
box.Length = 10
|
||||
box.Width = 10
|
||||
box.Height = 10
|
||||
|
||||
cylinder = doc.addObject("Part::Cylinder", "Cyl1")
|
||||
cylinder.Radius = 5
|
||||
cylinder.Height = 20
|
||||
cylinder.Placement.Base = App.Vector(20, 0, 0)
|
||||
|
||||
doc.recompute()
|
||||
|
||||
# Set up export parameters
|
||||
params = {{
|
||||
"directory": "{temp_export_dir}",
|
||||
"base_filename": "test_compound",
|
||||
"formats": ["stl"],
|
||||
"mesh_tolerance": 0.1,
|
||||
}}
|
||||
|
||||
# Create exporter with multiple objects
|
||||
exporter = MultiExporter([box, cylinder], params)
|
||||
exported_files, errors = exporter.export_all()
|
||||
|
||||
# Check file size (compound should be larger than single object)
|
||||
file_size = os.path.getsize(exported_files[0]) if exported_files else 0
|
||||
|
||||
# Clean up
|
||||
App.closeDocument("TestExportMultiObj")
|
||||
|
||||
_result_ = {{
|
||||
"exported_files": exported_files,
|
||||
"errors": errors,
|
||||
"file_exists": os.path.exists(exported_files[0]) if exported_files else False,
|
||||
"file_size": file_size,
|
||||
}}
|
||||
"""
|
||||
result = execute_code(xmlrpc_proxy, code)
|
||||
data = result.get("result", {})
|
||||
|
||||
assert len(data["exported_files"]) == 1
|
||||
assert len(data["errors"]) == 0
|
||||
assert data["file_exists"] is True
|
||||
# Compound file should have some reasonable size
|
||||
assert data["file_size"] > 100
|
||||
|
||||
def test_export_with_custom_tolerance(self, xmlrpc_proxy, temp_export_dir):
|
||||
"""Test that mesh tolerance affects output file size.
|
||||
|
||||
Note: FreeCAD's tessellate function has internal limits, so we need
|
||||
to use a very fine tolerance (0.01) to actually see more triangles
|
||||
compared to the default tessellation.
|
||||
"""
|
||||
code = f"""
|
||||
{MULTI_EXPORTER_CODE}
|
||||
|
||||
# Create a sphere (curved surface shows tolerance effect best)
|
||||
doc = App.newDocument("TestTolerance")
|
||||
sphere = doc.addObject("Part::Sphere", "TestSphere")
|
||||
sphere.Radius = 20
|
||||
doc.recompute()
|
||||
|
||||
# Export with coarse tolerance (uses default tessellation)
|
||||
params_coarse = {{
|
||||
"directory": "{temp_export_dir}",
|
||||
"base_filename": "sphere_coarse",
|
||||
"formats": ["stl"],
|
||||
"mesh_tolerance": 1.0,
|
||||
}}
|
||||
exporter_coarse = MultiExporter([sphere], params_coarse)
|
||||
coarse_files, _ = exporter_coarse.export_all()
|
||||
coarse_size = os.path.getsize(coarse_files[0]) if coarse_files else 0
|
||||
|
||||
# Export with very fine tolerance (0.01 required to see difference)
|
||||
params_fine = {{
|
||||
"directory": "{temp_export_dir}",
|
||||
"base_filename": "sphere_fine",
|
||||
"formats": ["stl"],
|
||||
"mesh_tolerance": 0.01,
|
||||
}}
|
||||
exporter_fine = MultiExporter([sphere], params_fine)
|
||||
fine_files, _ = exporter_fine.export_all()
|
||||
fine_size = os.path.getsize(fine_files[0]) if fine_files else 0
|
||||
|
||||
# Clean up
|
||||
App.closeDocument("TestTolerance")
|
||||
|
||||
_result_ = {{
|
||||
"coarse_size": coarse_size,
|
||||
"fine_size": fine_size,
|
||||
"fine_is_larger": fine_size > coarse_size,
|
||||
}}
|
||||
"""
|
||||
result = execute_code(xmlrpc_proxy, code)
|
||||
data = result.get("result", {})
|
||||
|
||||
# Fine tolerance (0.01) should produce larger file (more triangles)
|
||||
assert data["fine_is_larger"] is True
|
||||
assert data["fine_size"] > data["coarse_size"]
|
||||
|
||||
def test_export_empty_formats_list(self, xmlrpc_proxy, temp_export_dir):
|
||||
"""Test that empty formats list returns appropriate error."""
|
||||
code = f"""
|
||||
{MULTI_EXPORTER_CODE}
|
||||
|
||||
doc = App.newDocument("TestEmptyFormats")
|
||||
box = doc.addObject("Part::Box", "TestBox")
|
||||
doc.recompute()
|
||||
|
||||
params = {{
|
||||
"directory": "{temp_export_dir}",
|
||||
"base_filename": "test",
|
||||
"formats": [],
|
||||
"mesh_tolerance": 0.1,
|
||||
}}
|
||||
|
||||
exporter = MultiExporter([box], params)
|
||||
exported_files, errors = exporter.export_all()
|
||||
|
||||
App.closeDocument("TestEmptyFormats")
|
||||
|
||||
_result_ = {{
|
||||
"exported_files": exported_files,
|
||||
"errors": errors,
|
||||
}}
|
||||
"""
|
||||
result = execute_code(xmlrpc_proxy, code)
|
||||
data = result.get("result", {})
|
||||
|
||||
assert len(data["exported_files"]) == 0
|
||||
assert len(data["errors"]) == 1
|
||||
assert "No formats selected" in data["errors"][0]
|
||||
@@ -23,13 +23,9 @@ class TestInstallSyntax:
|
||||
"install::uninstall-mcp-server",
|
||||
"install::mcp-bridge-workbench",
|
||||
"install::uninstall-mcp-bridge-workbench",
|
||||
"install::macro-cut",
|
||||
"install::uninstall-macro-cut",
|
||||
"install::macro-export",
|
||||
"install::uninstall-macro-export",
|
||||
"install::macro-all",
|
||||
"install::uninstall-macro-all",
|
||||
"install::status",
|
||||
"install::uninstall",
|
||||
"install::cleanup",
|
||||
]
|
||||
|
||||
@pytest.mark.just_syntax
|
||||
@@ -77,3 +73,23 @@ class TestInstallRuntime:
|
||||
assert uninstall_result.success, (
|
||||
f"MCP server uninstall failed: {uninstall_result.stderr}"
|
||||
)
|
||||
|
||||
@pytest.mark.just_runtime
|
||||
def test_uninstall_runs(self, just: JustRunner) -> None:
|
||||
"""Uninstall command should run without error.
|
||||
|
||||
This command uninstalls all components. It may complete with warnings
|
||||
if nothing is installed, but should not error.
|
||||
"""
|
||||
result = just.run("install::uninstall", timeout=120)
|
||||
# Command should succeed even if nothing was installed
|
||||
assert result.success, f"Uninstall failed: {result.stderr}"
|
||||
|
||||
@pytest.mark.just_runtime
|
||||
def test_cleanup_runs(self, just: JustRunner) -> None:
|
||||
"""Cleanup command should run without error.
|
||||
|
||||
This command cleans up caches and temporary files.
|
||||
"""
|
||||
result = just.run("install::cleanup", timeout=60)
|
||||
assert result.success, f"Cleanup failed: {result.stderr}"
|
||||
|
||||
@@ -63,11 +63,17 @@ class TestModulesExist:
|
||||
# Map of modules to their expected commands (subset for validation)
|
||||
EXPECTED_COMMANDS: ClassVar[dict[str, list[str]]] = {
|
||||
"quality": ["check", "format", "lint", "typecheck", "security"],
|
||||
"testing": ["unit", "cov", "fast", "integration"],
|
||||
"testing": ["unit", "cov", "quick", "integration"],
|
||||
"dev": ["install-deps", "install-pre-commit", "clean"],
|
||||
"docker": ["build", "run", "clean"],
|
||||
"documentation": ["build", "serve", "open"],
|
||||
"install": ["mcp-server", "mcp-bridge-workbench", "status"],
|
||||
"install": [
|
||||
"mcp-server",
|
||||
"mcp-bridge-workbench",
|
||||
"status",
|
||||
"uninstall",
|
||||
"cleanup",
|
||||
],
|
||||
"mcp": ["run", "check"],
|
||||
"freecad": ["run-gui", "run-headless"],
|
||||
"release": ["status", "list-tags", "latest-versions"],
|
||||
@@ -110,7 +116,7 @@ class TestSyntaxValidation:
|
||||
# Testing commands
|
||||
"testing::unit",
|
||||
"testing::cov",
|
||||
"testing::fast",
|
||||
"testing::quick",
|
||||
"testing::verbose",
|
||||
# Dev commands
|
||||
"dev::install-deps",
|
||||
@@ -131,8 +137,6 @@ class TestSyntaxValidation:
|
||||
# Install commands
|
||||
"install::mcp-server",
|
||||
"install::mcp-bridge-workbench",
|
||||
"install::macro-cut",
|
||||
"install::macro-export",
|
||||
"install::status",
|
||||
# MCP commands
|
||||
"mcp::check",
|
||||
|
||||
@@ -44,13 +44,9 @@ class TestReleaseSyntax:
|
||||
RELEASE_COMMANDS: ClassVar[list[tuple[str, list[str]]]] = [
|
||||
# Version bump commands
|
||||
("bump-workbench", ["0.0.1-test"]),
|
||||
("bump-macro-magnets", ["0.0.1-test"]),
|
||||
("bump-macro-export", ["0.0.1-test"]),
|
||||
# Tag commands (require version argument)
|
||||
("tag-mcp-server", ["0.0.1-test"]),
|
||||
("tag-workbench", ["0.0.1-test"]),
|
||||
("tag-macro-magnets", ["0.0.1-test"]),
|
||||
("tag-macro-export", ["0.0.1-test"]),
|
||||
# Info commands
|
||||
("list-tags", []),
|
||||
("latest-versions", []),
|
||||
@@ -63,9 +59,9 @@ class TestReleaseSyntax:
|
||||
# Tag management
|
||||
("delete-tag", ["test-tag-v0.0.1"]),
|
||||
# Wiki commands
|
||||
("wiki-update", ["magnets"]),
|
||||
("wiki-show", ["magnets"]),
|
||||
("wiki-diff", ["magnets"]),
|
||||
("wiki-update", ["workbench"]),
|
||||
("wiki-show", ["workbench"]),
|
||||
("wiki-diff", ["workbench"]),
|
||||
]
|
||||
|
||||
@pytest.mark.just_syntax
|
||||
@@ -106,7 +102,7 @@ class TestReleaseReadOnly:
|
||||
@pytest.mark.just_runtime
|
||||
@pytest.mark.parametrize(
|
||||
"component",
|
||||
["mcp-server", "workbench", "macro-magnets", "macro-export"],
|
||||
["mcp-server", "workbench"],
|
||||
)
|
||||
def test_changes_since_works(self, just: JustRunner, component: str) -> None:
|
||||
"""changes-since should work for each component."""
|
||||
@@ -121,7 +117,7 @@ class TestReleaseReadOnly:
|
||||
@pytest.mark.just_runtime
|
||||
@pytest.mark.parametrize(
|
||||
"component",
|
||||
["mcp-server", "workbench", "macro-magnets", "macro-export"],
|
||||
["mcp-server", "workbench"],
|
||||
)
|
||||
def test_draft_notes_works(self, just: JustRunner, component: str) -> None:
|
||||
"""draft-notes should work for each component."""
|
||||
@@ -135,8 +131,6 @@ class TestReleaseReadOnly:
|
||||
[
|
||||
("mcp-server", "1.0.0"),
|
||||
("workbench", "1.0.0"),
|
||||
("macro-magnets", "1.0.0"),
|
||||
("macro-export", "1.0.0"),
|
||||
],
|
||||
)
|
||||
def test_dry_run_tag_shows_info(
|
||||
@@ -148,10 +142,9 @@ class TestReleaseReadOnly:
|
||||
assert "Would create tag" in result.stdout
|
||||
|
||||
@pytest.mark.just_runtime
|
||||
@pytest.mark.parametrize("macro", ["magnets", "export"])
|
||||
def test_wiki_show_works(self, just: JustRunner, macro: str) -> None:
|
||||
def test_wiki_show_works(self, just: JustRunner) -> None:
|
||||
"""wiki-show should display wiki source content."""
|
||||
result = just.run("release::wiki-show", macro, timeout=10)
|
||||
result = just.run("release::wiki-show", "workbench", timeout=10)
|
||||
assert result.success, f"wiki-show failed: {result.stderr}"
|
||||
assert "Wiki Source" in result.stdout
|
||||
|
||||
@@ -175,15 +168,6 @@ class TestReleaseBumpCommands:
|
||||
/ "addon/FreecadRobustMCPBridge/freecad_mcp_bridge/__init__.py",
|
||||
PROJECT_ROOT / "addon/FreecadRobustMCPBridge/wiki-source.txt",
|
||||
PROJECT_ROOT / "package.xml",
|
||||
# Cut Object for Magnets macro files
|
||||
PROJECT_ROOT / "macros/Cut_Object_for_Magnets/CutObjectForMagnets.FCMacro",
|
||||
PROJECT_ROOT
|
||||
/ "macros/Cut_Object_for_Magnets/README-CutObjectForMagnets.md",
|
||||
PROJECT_ROOT / "macros/Cut_Object_for_Magnets/wiki-source.txt",
|
||||
# Multi Export macro files
|
||||
PROJECT_ROOT / "macros/Multi_Export/MultiExport.FCMacro",
|
||||
PROJECT_ROOT / "macros/Multi_Export/README-MultiExport.md",
|
||||
PROJECT_ROOT / "macros/Multi_Export/wiki-source.txt",
|
||||
]
|
||||
|
||||
backups: dict[Path, str] = {}
|
||||
@@ -214,38 +198,6 @@ class TestReleaseBumpCommands:
|
||||
content = init_file.read_text()
|
||||
assert "99.99.99-test" in content
|
||||
|
||||
@pytest.mark.just_runtime
|
||||
@pytest.mark.just_release
|
||||
def test_bump_macro_magnets_modifies_files(
|
||||
self, just: JustRunner, backup_and_restore_files: None
|
||||
) -> None:
|
||||
"""bump-macro-magnets should modify version files."""
|
||||
result = just.run("release::bump-macro-magnets", "99.99.99-test", timeout=30)
|
||||
assert result.success, f"bump-macro-magnets failed: {result.stderr}"
|
||||
assert "Version bump complete" in result.stdout
|
||||
|
||||
# Verify version was updated
|
||||
macro_file = (
|
||||
PROJECT_ROOT / "macros/Cut_Object_for_Magnets/CutObjectForMagnets.FCMacro"
|
||||
)
|
||||
content = macro_file.read_text()
|
||||
assert "99.99.99-test" in content
|
||||
|
||||
@pytest.mark.just_runtime
|
||||
@pytest.mark.just_release
|
||||
def test_bump_macro_export_modifies_files(
|
||||
self, just: JustRunner, backup_and_restore_files: None
|
||||
) -> None:
|
||||
"""bump-macro-export should modify version files."""
|
||||
result = just.run("release::bump-macro-export", "99.99.99-test", timeout=30)
|
||||
assert result.success, f"bump-macro-export failed: {result.stderr}"
|
||||
assert "Version bump complete" in result.stdout
|
||||
|
||||
# Verify version was updated
|
||||
macro_file = PROJECT_ROOT / "macros/Multi_Export/MultiExport.FCMacro"
|
||||
content = macro_file.read_text()
|
||||
assert "99.99.99-test" in content
|
||||
|
||||
|
||||
class TestReleaseTagCommands:
|
||||
"""Tests for tag creation commands.
|
||||
@@ -347,10 +299,6 @@ class TestReleaseValidation:
|
||||
"mcp-server",
|
||||
"server",
|
||||
"workbench",
|
||||
"macro-magnets",
|
||||
"magnets",
|
||||
"macro-export",
|
||||
"export",
|
||||
],
|
||||
)
|
||||
def test_changes_since_component_aliases(
|
||||
|
||||
@@ -22,11 +22,12 @@ class TestTestingSyntax:
|
||||
TESTING_COMMANDS: ClassVar[list[str]] = [
|
||||
"testing::unit",
|
||||
"testing::cov",
|
||||
"testing::fast",
|
||||
"testing::quick",
|
||||
"testing::integration",
|
||||
"testing::verbose",
|
||||
"testing::all",
|
||||
"testing::watch",
|
||||
"testing::check-deps",
|
||||
"testing::integration-freecad-auto",
|
||||
"testing::just-syntax",
|
||||
"testing::just-runtime",
|
||||
@@ -73,11 +74,11 @@ class TestTestingRuntime:
|
||||
assert_command_executed(result, "testing::unit")
|
||||
|
||||
@pytest.mark.just_runtime
|
||||
def test_fast_command_recognizes_markers(self, just: JustRunner) -> None:
|
||||
"""Fast test command should recognize the 'not slow' marker."""
|
||||
def test_quick_command_recognizes_markers(self, just: JustRunner) -> None:
|
||||
"""Quick test command should recognize the 'not slow' marker."""
|
||||
result = just.run(
|
||||
"testing::fast",
|
||||
"testing::quick",
|
||||
timeout=60,
|
||||
env={"PYTEST_ADDOPTS": "--collect-only -q"},
|
||||
)
|
||||
assert_command_executed(result, "testing::fast")
|
||||
assert_command_executed(result, "testing::quick")
|
||||
|
||||
@@ -373,9 +373,9 @@ class TestFreecadResources:
|
||||
"""freecad://macros should return macro list."""
|
||||
mock_macros = [
|
||||
MacroInfo(
|
||||
name="MultiExport",
|
||||
path="/home/user/.local/share/FreeCAD/Macro/MultiExport.FCMacro",
|
||||
description="Export to multiple formats",
|
||||
name="ExportSTL",
|
||||
path="/home/user/.local/share/FreeCAD/Macro/ExportSTL.FCMacro",
|
||||
description="Export objects to STL",
|
||||
is_system=False,
|
||||
),
|
||||
MacroInfo(
|
||||
@@ -392,7 +392,7 @@ class TestFreecadResources:
|
||||
data = json.loads(result)
|
||||
|
||||
assert len(data) == 2
|
||||
assert data[0]["name"] == "MultiExport"
|
||||
assert data[0]["name"] == "ExportSTL"
|
||||
assert data[0]["is_system"] is False
|
||||
assert data[1]["is_system"] is True
|
||||
|
||||
|
||||
@@ -58,9 +58,9 @@ class TestMacroTools:
|
||||
"""list_macros should return macro info."""
|
||||
mock_macros = [
|
||||
MacroInfo(
|
||||
name="MultiExport",
|
||||
path="/home/user/.FreeCAD/Macro/MultiExport.FCMacro",
|
||||
description="Export to multiple formats",
|
||||
name="ExportSTL",
|
||||
path="/home/user/.FreeCAD/Macro/ExportSTL.FCMacro",
|
||||
description="Export objects to STL",
|
||||
is_system=False,
|
||||
),
|
||||
MacroInfo(
|
||||
@@ -76,7 +76,7 @@ class TestMacroTools:
|
||||
result = await list_macros()
|
||||
|
||||
assert len(result) == 2
|
||||
assert result[0]["name"] == "MultiExport"
|
||||
assert result[0]["name"] == "ExportSTL"
|
||||
assert result[0]["is_system"] is False
|
||||
assert result[1]["name"] == "SystemMacro"
|
||||
assert result[1]["is_system"] is True
|
||||
@@ -96,11 +96,11 @@ class TestMacroTools:
|
||||
)
|
||||
|
||||
run_macro = register_tools["run_macro"]
|
||||
result = await run_macro(macro_name="MultiExport")
|
||||
result = await run_macro(macro_name="ExportSTL")
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["stdout"] == "Exported 3 objects\n"
|
||||
mock_bridge.run_macro.assert_called_once_with("MultiExport", None)
|
||||
mock_bridge.run_macro.assert_called_once_with("ExportSTL", None)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_macro_with_args(self, register_tools, mock_bridge):
|
||||
|
||||
Reference in New Issue
Block a user