Run the tests
The suite is small on purpose. It was triaged down from ~170 generated tests (most of which tested mocks against mocks, or called APIs that never existed) to tests that each verify something real. On a machine without FreeCAD or the Docker image: 63 passed, 34 skipped, about two seconds.
Layout
Section titled “Layout”| Path | What it verifies | Runs where |
|---|---|---|
tests/test_freecad_server.py |
Core 6 tools via in-memory MCP client | anywhere (mocked FreeCAD) |
tests/test_advanced_cad_tools.py |
Advanced 7 tools via in-memory MCP client | anywhere (mocked FreeCAD) |
tests/test_mcp_protocol_compliance.py |
Initialize handshake, capabilities, schemas, concurrency | anywhere |
tests/security/ |
Path traversal, injection, resource limits — asserts the server actually blocks | anywhere |
tests/cross_platform/ |
Path/encoding/platform behavior | anywhere |
tests/integration/ |
Real file opens/conversions with on-disk assertions | Docker only (skips without real FreeCAD) |
tests/test_docker_*.py |
Container build/runtime + MCP-over-HTTP in container | needs Docker daemon + image (skips otherwise) |
Make targets
Section titled “Make targets”make test # fast default: everything except docker/slow (~2s)make test-unit # just the 13-tool suitesmake test-mcp # protocol compliancemake test-security # security auditsmake test-docker # docker-gated tests (skip cleanly if image absent)make test-in-docker # the real-FreeCAD integration suite, inside the containermake coverage # terminal + HTML coveragemake reports # full HTML report (reports/test_report.html)Expensive inline Docker image builds are double-gated: they only run with CAD_MCP_DOCKER_BUILD_TESTS=1 and -m slow selected.
Real-file fixtures
Section titled “Real-file fixtures”The integration tier also runs against a fixture zoo of real drawings in a gitignored data/ directory: a DXF version ladder (R12 → 2018 ASCII, plus 2018 binary), two negative-path files that bracket what recover-mode loading can and cannot save, and contrasting real-world profiles — a 941-layer sparse civil plan set, a 2-layer 16k-entity mechanical drawing, and a 12 MB STEP solid for the FreeCAD tier. The reasoning behind the zoo’s composition is covered in The two-tier testing philosophy.
These files aren’t in the repository — tests skip cleanly when data/ is absent, per the skip contract below. To assemble your own corpus, start with the public DWG sources in the DWG guide.
Skips are contractual, not noise
Section titled “Skips are contractual, not noise”Skips are intentional and reasoned — run with -rs to see why each one skipped. The rule:
- A skip on a machine without FreeCAD/Docker is correct behavior.
- A skip that should have run (Docker tests skipping when the image exists) is a bug.
- A failure is never acceptable on a clean checkout.
FastMCP 3.x testing conventions
Section titled “FastMCP 3.x testing conventions”These conventions came out of real breakage; each one has a reason:
- In-memory client, no transport. Fixtures return the server; open
async with Client(server)inside the test. Opening clients in fixtures causes event-loop issues — this is from the FastMCP docs, not superstition. - Calls nest one level. Tools take a single Pydantic parameter, so calls look like
call_tool(name, {"operation": {...}}). - Assert on
result.data(the hydrated return value); error paths raisefastmcp.exceptions.ToolError. - Mock the backend, never the framework. FreeCAD is mocked at the
sys.moduleslevel viatests/freecad_mocks.py; FastMCP itself runs for real. Mock return values must be JSON-serializable — the real client serializes results, and a strayMagicMockin a return dict fails the call. - Install mocks before importing
src.freecad_server. The module bindsFreeCAD = Noneat first import and poisons the session otherwise.
The reasoning behind mock-the-backend-not-the-framework — and the cautionary tale of ~100 tests written against a test client that never existed — is covered in The two-tier testing philosophy and Phantom APIs.