kicad-mcp/tests/test_schematic.py
Ryan Malloy 52ff054f43 Fix coordinate precision and per-schematic sidecar isolation
Reduce _rc() and transform_pin_to_schematic() rounding from 3 to 2
decimal places to match KiCad's 0.01mm coordinate quantum — prevents
union-find misses when wire endpoints and sexp-parsed pin positions
differ at the sub-quantum level.

Use schematic stem as subdirectory inside .mckicad/ so multi-sheet
analysis outputs (connectivity.json, etc.) don't collide.
2026-03-04 18:22:21 -07:00

171 lines
5.9 KiB
Python

"""Tests for schematic tools (kicad-sch-api integration)."""
import os
import pytest
from tests.conftest import requires_sch_api
@pytest.mark.unit
def test_create_schematic(tmp_output_dir):
"""create_schematic should produce a .kicad_sch file."""
from mckicad.tools.schematic import create_schematic
output_path = os.path.join(tmp_output_dir, "test.kicad_sch")
result = create_schematic(name="test_circuit", output_path=output_path)
assert result["success"] is True
assert os.path.exists(output_path)
@pytest.mark.unit
def test_create_schematic_invalid_path():
"""create_schematic should fail gracefully for invalid paths."""
from mckicad.tools.schematic import create_schematic
result = create_schematic(name="x", output_path="/nonexistent/dir/test.kicad_sch")
assert result["success"] is False
assert "error" in result
@pytest.mark.unit
def test_search_components():
"""search_components should return results for common queries."""
from mckicad.tools.schematic import search_components
result = search_components(query="resistor")
# Should succeed even if no libs installed (returns empty results)
assert "success" in result
@pytest.mark.unit
def test_list_components_empty_schematic(tmp_output_dir):
"""list_components on new empty schematic should return empty list."""
from mckicad.tools.schematic import create_schematic, list_components
path = os.path.join(tmp_output_dir, "empty.kicad_sch")
create_schematic(name="empty", output_path=path)
result = list_components(schematic_path=path)
if result["success"]:
assert result.get("count", 0) == 0
@requires_sch_api
@pytest.mark.unit
def test_list_components_single_lookup(populated_schematic):
"""list_components with reference param should return one component."""
from mckicad.tools.schematic import list_components
result = list_components(schematic_path=populated_schematic, reference="R1")
assert result["success"] is True
assert result["count"] == 1
assert result["components"][0]["reference"] == "R1"
@requires_sch_api
@pytest.mark.unit
def test_list_components_single_lookup_not_found(populated_schematic):
"""list_components with nonexistent reference should fail."""
from mckicad.tools.schematic import list_components
result = list_components(schematic_path=populated_schematic, reference="Z99")
assert result["success"] is False
@requires_sch_api
@pytest.mark.unit
def test_get_schematic_info_compact(populated_schematic):
"""get_schematic_info should return compact output."""
from mckicad.tools.schematic import get_schematic_info
result = get_schematic_info(schematic_path=populated_schematic)
assert result["success"] is True
assert "statistics" in result
assert "validation" in result
assert "unique_symbol_count" in result
@requires_sch_api
@pytest.mark.unit
def test_get_component_detail(populated_schematic):
"""get_component_detail should return full info for one component."""
from mckicad.tools.schematic import get_component_detail
result = get_component_detail(schematic_path=populated_schematic, reference="R1")
assert result["success"] is True
assert result["component"]["reference"] == "R1"
assert result["component"]["lib_id"] == "Device:R"
@requires_sch_api
@pytest.mark.unit
def test_get_component_detail_not_found(populated_schematic):
"""get_component_detail for missing component should fail."""
from mckicad.tools.schematic import get_component_detail
result = get_component_detail(schematic_path=populated_schematic, reference="Z99")
assert result["success"] is False
@requires_sch_api
@pytest.mark.unit
def test_get_schematic_hierarchy(populated_schematic):
"""get_schematic_hierarchy should return at least the root sheet."""
from mckicad.tools.schematic import get_schematic_hierarchy
result = get_schematic_hierarchy(schematic_path=populated_schematic)
assert result["success"] is True
assert result["hierarchy"]["name"] == "root"
assert result["hierarchy"]["total_sheets"] >= 1
@pytest.mark.unit
def test_file_output_infrastructure(tmp_output_dir):
"""write_detail_file should create .mckicad/{stem}/ sidecar directory and file."""
from mckicad.utils.file_utils import write_detail_file
fake_sch = os.path.join(tmp_output_dir, "test.kicad_sch")
# File doesn't need to exist for write_detail_file
open(fake_sch, "w").close()
data = {"test": "data", "items": [1, 2, 3]}
path = write_detail_file(fake_sch, "test_output.json", data)
assert os.path.isfile(path)
assert ".mckicad" in path
assert os.path.join(".mckicad", "test", "test_output.json") in path
@pytest.mark.unit
def test_file_output_cwd_fallback(tmp_output_dir, monkeypatch):
"""write_detail_file with None path should use flat CWD/.mckicad/."""
from mckicad.utils.file_utils import write_detail_file
monkeypatch.chdir(tmp_output_dir)
path = write_detail_file(None, "test_cwd.json", {"test": True})
assert os.path.isfile(path)
assert ".mckicad" in path
# None path stays flat — no stem subdirectory
assert path.endswith(os.path.join(".mckicad", "test_cwd.json"))
@pytest.mark.unit
def test_sidecar_per_schematic_isolation(tmp_output_dir):
"""Detail files for different schematics land in separate subdirectories."""
from mckicad.utils.file_utils import write_detail_file
sch_a = os.path.join(tmp_output_dir, "power.kicad_sch")
sch_b = os.path.join(tmp_output_dir, "esp32_p4_core.kicad_sch")
open(sch_a, "w").close()
open(sch_b, "w").close()
path_a = write_detail_file(sch_a, "connectivity.json", {"nets": {}})
path_b = write_detail_file(sch_b, "connectivity.json", {"nets": {}})
assert os.path.isfile(path_a)
assert os.path.isfile(path_b)
assert path_a != path_b
assert os.path.join(".mckicad", "power", "connectivity.json") in path_a
assert os.path.join(".mckicad", "esp32_p4_core", "connectivity.json") in path_b