"""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 class TestAddLabelPersistence: """Labels added via add_label must actually appear in the saved file.""" @pytest.mark.unit def test_local_label_persists(self, tmp_output_dir): from mckicad.tools.schematic import add_label path = os.path.join(tmp_output_dir, "label_test.kicad_sch") with open(path, "w") as f: f.write("(kicad_sch\n (version 20231120)\n (uuid \"abc\")\n)\n") result = add_label(schematic_path=path, text="SPI_CLK", x=100.0, y=200.0) assert result["success"] is True assert result["label_type"] == "local" assert result["engine"] == "sexp-direct" with open(path) as f: content = f.read() assert '(label "SPI_CLK"' in content assert "(at 100 200 0)" in content @pytest.mark.unit def test_global_label_persists(self, tmp_output_dir): from mckicad.tools.schematic import add_label path = os.path.join(tmp_output_dir, "glabel_test.kicad_sch") with open(path, "w") as f: f.write("(kicad_sch\n (version 20231120)\n (uuid \"abc\")\n)\n") result = add_label( schematic_path=path, text="VBUS_OUT", x=187.96, y=114.3, global_label=True, ) assert result["success"] is True assert result["label_type"] == "global" with open(path) as f: content = f.read() assert '(global_label "VBUS_OUT"' in content assert "(shape bidirectional)" in content assert "Intersheetrefs" in content @pytest.mark.unit def test_multiple_labels_all_persist(self, tmp_output_dir): from mckicad.tools.schematic import add_label path = os.path.join(tmp_output_dir, "multi_label.kicad_sch") with open(path, "w") as f: f.write("(kicad_sch\n (version 20231120)\n (uuid \"abc\")\n)\n") labels = ["NET_A", "NET_B", "NET_C", "GLOBAL_D"] for i, name in enumerate(labels): is_global = name.startswith("GLOBAL_") result = add_label( schematic_path=path, text=name, x=100.0 + i * 10, y=200.0, global_label=is_global, ) assert result["success"] is True with open(path) as f: content = f.read() for name in labels: if name.startswith("GLOBAL_"): assert f'(global_label "{name}"' in content else: assert f'(label "{name}"' in content @pytest.mark.unit def test_empty_text_rejected(self, tmp_output_dir): from mckicad.tools.schematic import add_label path = os.path.join(tmp_output_dir, "empty_label.kicad_sch") with open(path, "w") as f: f.write("(kicad_sch\n (version 20231120)\n (uuid \"abc\")\n)\n") result = add_label(schematic_path=path, text="", x=0, y=0) assert result["success"] is False