Files
autocad-mcp-pro/tests/test_delivery.py
T
Umutcan EdizsalanandCursor 3cbe30df54 feat(server): tool profiles, paper-space layouts + viewports, ISO 286 fits, opt-in COM 3D solids
- TOOL_PROFILE=lean|core|full applied in the lifespan via server-level enable/disable; system_about reports the active profile; opt-in solid_* tools are hidden while ENABLE_3D=false
- layouts: layout_list/create/set_current + viewport_create (scaled model viewports) on both backends; drawing_export_pdf(layout=...); ezdxf uses TILEMODE + active-layout binding, COM switches ActiveLayout and restores it
- ISO 286: engineering/fits.py authored IT4-11 + fundamental-deviation tables (1-500 mm; shafts d..p/h/js, holes D..H/JS) with clear out-of-scope errors; dimension_linear/radius/diameter gained fit= resolved from the measured nominal
- tolerances: DIMTM now signed in deviation/limit modes so double-positive fits (p6 +0.035/+0.022) render correctly; legacy positive-magnitude contract preserved
- solids: solid_box/cylinder/extrude/revolve/boolean native on COM (AddBox/AddCylinder/AddRegion+AddExtrudedSolid/AddRevolvedSolid/Boolean); ezdxf reports the honest solid_3d capability boundary
- capability maps: paper_space native, new viewport_render feature, ENABLE_3D-gated solid_3d
- 60 new tests (profiles, layouts, viewports, layout PDF, fits, gating); version pins in tests now read version.__version__

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 15:15:45 +03:00

139 lines
4.4 KiB
Python

"""Release delivery contract: artifacts, hashes, validation and DXF parity."""
from __future__ import annotations
import hashlib
import json
import pytest
import server
from engineering.delivery import compare_drawing_snapshots, deliver_drawing, drawing_snapshot
from engineering.layers import ensure_engineering_layers, ensure_standard_linetypes
from version import __version__
async def _clean_drawing(backend) -> None:
await ensure_standard_linetypes(backend)
await ensure_engineering_layers(backend)
await backend.entity_create_line(0, 0, 40, 0, layer="GEOMETRY")
@pytest.mark.asyncio
async def test_delivery_writes_hashed_manifest_and_reopen_parity(backend, tmp_path):
await _clean_drawing(backend)
result = await deliver_drawing(
backend,
tmp_path,
formats=["dxf"],
min_score=0,
strict_critique=False,
)
assert result.status == "success"
manifest_path = tmp_path / "manifest.json"
validation_path = tmp_path / "validation.json"
assert manifest_path.is_file()
assert validation_path.is_file()
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
dxf = next(item for item in manifest["artifacts"] if item["format"] == "dxf")
artifact_path = tmp_path / dxf["filename"]
assert dxf["status"] == "created"
assert dxf["sha256"] == hashlib.sha256(artifact_path.read_bytes()).hexdigest()
assert manifest["parity"]["ok"] is True
assert manifest["version"] == __version__
assert manifest["capabilities"]["backend"] == "ezdxf"
@pytest.mark.asyncio
async def test_delivery_keeps_artifacts_when_validation_fails(backend, tmp_path):
await _clean_drawing(backend)
await backend.construction_xline(0, 0, 45)
result = await deliver_drawing(
backend,
tmp_path,
formats=["dxf"],
min_score=100,
strict_critique=True,
)
assert result.status == "failed_validation"
assert (tmp_path / "drawing.dxf").is_file()
manifest = json.loads((tmp_path / "manifest.json").read_text(encoding="utf-8"))
assert manifest["status"] == "failed_validation"
assert any(issue["focus"] == "construction_left" for issue in manifest["critique"])
def test_snapshot_comparison_reports_type_and_bounds_drift():
source = {
"entity_count": 2,
"types": {"LINE": 2},
"layers": {"GEOMETRY": 2},
"extents_min": [0.0, 0.0],
"extents_max": [10.0, 5.0],
}
reopened = {
"entity_count": 2,
"types": {"LINE": 1, "CIRCLE": 1},
"layers": {"GEOMETRY": 2},
"extents_min": [0.0, 0.0],
"extents_max": [10.5, 5.0],
}
result = compare_drawing_snapshots(source, reopened)
assert result["ok"] is False
assert {item["field"] for item in result["differences"]} == {"types", "extents_max"}
@pytest.mark.asyncio
async def test_snapshot_parity_rejects_incomplete_entity_inventory(backend, monkeypatch):
await backend.entity_create_line(0, 0, 10, 0)
await backend.entity_create_circle(5, 5, 2)
original_entity_list = backend.entity_list
async def incomplete_entity_list(*args, **kwargs):
return (await original_entity_list(*args, **kwargs))[:1]
monkeypatch.setattr(backend, "entity_list", incomplete_entity_list)
source = await drawing_snapshot(backend)
reopened = dict(source, inventory_complete=True, listed_entity_count=2)
result = compare_drawing_snapshots(source, reopened)
assert source["entity_count"] == 2
assert source["listed_entity_count"] == 1
assert source["inventory_complete"] is False
assert result["ok"] is False
assert "source_inventory_complete" in {item["field"] for item in result["differences"]}
@pytest.mark.asyncio
async def test_delivery_rejects_unknown_format(backend, tmp_path):
await _clean_drawing(backend)
with pytest.raises(ValueError, match="Unsupported delivery format"):
await deliver_drawing(backend, tmp_path, formats=["step"])
@pytest.mark.asyncio
async def test_server_drawing_deliver_exposes_manifest_tool(backend, tmp_path):
await _clean_drawing(backend)
class Ctx:
lifespan_context = {"backend": backend}
result = await server.drawing_deliver(
output_dir=str(tmp_path),
formats=["dxf"],
min_score=0,
strict_critique=False,
ctx=Ctx(),
)
assert result["status"] == "success"
assert result["manifest_path"].endswith("manifest.json")