Files
autocad-mcp-pro/tests/test_sprint4_validator.py
Umutcan EdizsalanandCursor 9ec6c4a989 style: apply ruff format across the repository
ruff format was never run repo-wide; CI now enforces 'ruff format --check', so normalize every module once. No behavior changes.

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

161 lines
5.2 KiB
Python

"""Sprint-4 R27 regression tests for engineering.validator step 5.
R27: title/helix consistency must match SPUR / HELICAL as whole words (not
substrings) and scope the scan to title-block text where possible.
"""
import pytest
from engineering.layers import ensure_engineering_layers, ensure_standard_linetypes
from engineering.validator import DrawingValidator
pytestmark = pytest.mark.asyncio
def _codes(result) -> set[str]:
return {f.code for f in result.findings}
async def _ensure_hidden_linetype(backend) -> None:
"""ezdxf doesn't ship a HIDDEN pattern; add a synthetic one."""
doc = backend._require_doc()
if "HIDDEN" not in doc.linetypes:
doc.linetypes.add(
"HIDDEN",
pattern=[6.35, 5.0, -1.27],
description="Hidden _ _ _ _ _ _",
)
async def _bootstrap(backend):
await ensure_standard_linetypes(backend)
await _ensure_hidden_linetype(backend)
await ensure_engineering_layers(backend)
async def test_spur_as_substring_does_not_false_match(backend, tmp_path):
"""R27: 'SPURIOUS' contains 'spur' but must NOT trip a SPUR mismatch."""
await _bootstrap(backend)
await backend.entity_create_text(
text="SPURIOUS HELICAL GEAR",
x=0,
y=0,
height=5,
layer="TEXT",
)
await backend.drawing_save_as(str(tmp_path / "spurious.dxf"))
result = await DrawingValidator().run(backend, expected={"helix_angle": 15})
codes = _codes(result)
assert "title_says_spur_for_helical" not in codes
assert "title_helical_spur" not in codes
async def test_genuine_spur_title_flags_when_helical_expected(backend, tmp_path):
"""R27: a real 'SPUR GEAR' title still flags the mismatch when helix declared."""
await _bootstrap(backend)
await backend.entity_create_text(
text="SPUR GEAR",
x=0,
y=0,
height=5,
layer="TEXT",
)
await backend.drawing_save_as(str(tmp_path / "spur.dxf"))
result = await DrawingValidator().run(backend, expected={"helix_angle": 15})
codes = _codes(result)
assert "title_says_spur_for_helical" in codes
assert result.ok is False
async def test_spur_word_in_titleblock_layer_flags(backend, tmp_path):
"""R27: title-block-scoped scan — SPUR on TITLEBLOCK layer still flags."""
await _bootstrap(backend)
await backend.entity_create_text(
text="SPUR GEAR",
x=0,
y=0,
height=5,
layer="TITLEBLOCK",
)
await backend.drawing_save_as(str(tmp_path / "spur_tb.dxf"))
result = await DrawingValidator().run(backend, expected={"helix_angle": 15})
assert "title_says_spur_for_helical" in _codes(result)
async def test_substring_in_titleblock_layer_does_not_false_match(backend, tmp_path):
"""R27: 'SPURIOUS' on TITLEBLOCK layer must not trip a SPUR match either."""
await _bootstrap(backend)
await backend.entity_create_text(
text="SPURIOUS NOTE",
x=0,
y=0,
height=5,
layer="TITLEBLOCK",
)
await backend.drawing_save_as(str(tmp_path / "spurious_tb.dxf"))
result = await DrawingValidator().run(backend, expected={"helix_angle": 15})
codes = _codes(result)
assert "title_says_spur_for_helical" not in codes
assert "title_helical_spur" not in codes
async def test_helical_spur_combo_still_flags_invalid(backend, tmp_path):
"""R27: both whole words present -> invalid combo error preserved."""
await _bootstrap(backend)
await backend.entity_create_text(
text="HELICAL SPUR GEAR",
x=0,
y=0,
height=5,
layer="TEXT",
)
await backend.drawing_save_as(str(tmp_path / "combo.dxf"))
result = await DrawingValidator().run(backend, expected={"helix_angle": 15})
assert "title_helical_spur" in _codes(result)
assert result.ok is False
async def test_no_helix_expected_skips_title_check(backend, tmp_path):
"""Gating preserved: without helix_angle, a SPUR title raises no title finding."""
await _bootstrap(backend)
await backend.entity_create_text(
text="SPUR GEAR",
x=0,
y=0,
height=5,
layer="TEXT",
)
await backend.drawing_save_as(str(tmp_path / "nohelix.dxf"))
result = await DrawingValidator().run(backend)
codes = _codes(result)
assert "title_says_spur_for_helical" not in codes
assert "title_helical_spur" not in codes
async def test_titleblock_layer_takes_precedence_over_text_layer(backend, tmp_path):
"""R27 scoping: when title-block text exists, the TEXT-layer scan is not used.
SPUR sits on the generic TEXT layer (a body note), while the title block on
TITLEBLOCK is clean — the scoped scan should ignore the body note.
"""
await _bootstrap(backend)
await backend.entity_create_text(
text="HELICAL GEAR",
x=0,
y=0,
height=5,
layer="TITLEBLOCK",
)
await backend.entity_create_text(
text="SPUR TOOTH DETAIL NOTE",
x=0,
y=-20,
height=3.5,
layer="TEXT",
)
await backend.drawing_save_as(str(tmp_path / "scope.dxf"))
result = await DrawingValidator().run(backend, expected={"helix_angle": 15})
codes = _codes(result)
assert "title_says_spur_for_helical" not in codes
assert "title_helical_spur" not in codes