Skip to content

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.

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)
Terminal window
make test # fast default: everything except docker/slow (~2s)
make test-unit # just the 13-tool suites
make test-mcp # protocol compliance
make test-security # security audits
make test-docker # docker-gated tests (skip cleanly if image absent)
make test-in-docker # the real-FreeCAD integration suite, inside the container
make coverage # terminal + HTML coverage
make 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.

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 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.

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 raise fastmcp.exceptions.ToolError.
  • Mock the backend, never the framework. FreeCAD is mocked at the sys.modules level via tests/freecad_mocks.py; FastMCP itself runs for real. Mock return values must be JSON-serializable — the real client serializes results, and a stray MagicMock in a return dict fails the call.
  • Install mocks before importing src.freecad_server. The module binds FreeCAD = None at 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.