Rename the core package to periscopex, update UI/docs/Docker/deploy defaults to periscope.michelebigi.it, and keep legacy version/storage key aliases so existing projects keep working. Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
3.7 KiB
Python
108 lines
3.7 KiB
Python
"""E2 cad-bridge JSON and KiCad plugin focus helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from backend.periscopex.cad_bridge import annotate_findings_cad, build_cad_bridge
|
|
from backend.periscopex.models import CadIndexEntry, Finding, ValidationReport
|
|
from backend.periscopex.parsers_kicad import kicad_part_fields, parse_kicad
|
|
from plugins.kicad.focus import find_bridge_file, focus_target, load_bridge
|
|
|
|
|
|
def _f(**kwargs) -> Finding:
|
|
defaults = dict(designator="U3", finding="mux", status="WARNING")
|
|
defaults.update(kwargs)
|
|
return Finding(**defaults)
|
|
|
|
|
|
def test_bridge_exports_version_and_strips_pin_prefix():
|
|
report = ValidationReport(
|
|
project="p", timestamp="t",
|
|
findings=[_f(
|
|
finding_id="U3-001", rule_id="PE-MUX-001",
|
|
pins=["U3.12"], cad_sheet="power.kicad_sch",
|
|
cad_uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
|
net="UART5_TX",
|
|
)],
|
|
summary={"total": 1},
|
|
)
|
|
payload = build_cad_bridge(report, "proj-1", url_base="https://app/report")
|
|
assert payload["version"] == 1
|
|
assert payload["project_id"] == "proj-1"
|
|
row = payload["findings"][0]
|
|
assert row["pins"] == ["12"]
|
|
assert row["sheet"] == "power.kicad_sch"
|
|
assert row["uuid"].startswith("aaaa")
|
|
assert row["severity"] == "warning"
|
|
assert "finding=U3-001" in row["url"]
|
|
assert row["target"] == "sch"
|
|
|
|
|
|
def test_missing_uuid_stays_empty_and_pcb_rule_targets_board():
|
|
report = ValidationReport(
|
|
project="p", timestamp="t",
|
|
findings=[_f(rule_id="PE-PLC-001", pins=["1"], status="ERROR")],
|
|
summary={"total": 1},
|
|
)
|
|
row = build_cad_bridge(report, "x")["findings"][0]
|
|
assert row["uuid"] == ""
|
|
assert row["sheet"] == ""
|
|
assert row["target"] == "pcb"
|
|
assert row["severity"] == "error"
|
|
|
|
|
|
def test_annotate_does_not_overwrite_existing_cad_fields():
|
|
idx = {"U3": CadIndexEntry(uuid="from-index", sheet="child.kicad_sch")}
|
|
f = _f(cad_uuid="already", cad_sheet=None)
|
|
annotate_findings_cad([f], idx)
|
|
assert f.cad_uuid == "already"
|
|
assert f.cad_sheet == "child.kicad_sch"
|
|
|
|
|
|
def test_kicad_sch_fields_include_uuid_and_child_sheet(tmp_path: Path):
|
|
from tests.test_kicad_parser import _resistor, _sch
|
|
|
|
child = tmp_path / "analog.kicad_sch"
|
|
child.write_text(_sch(
|
|
_resistor("U3", "MCU"),
|
|
"""
|
|
(global_label "GND" (at 0 3.81 0) (uuid "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"))
|
|
""",
|
|
))
|
|
root = tmp_path / "root.kicad_sch"
|
|
root.write_text(_sch(
|
|
_resistor("R1", "10k"),
|
|
"""
|
|
(global_label "GND" (at 0 3.81 0) (uuid "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"))
|
|
(sheet
|
|
(at 50 0)
|
|
(size 20 20)
|
|
(property "Sheetname" "Analog" (at 50 0 0) (effects (font (size 1.27 1.27))))
|
|
(property "Sheetfile" "analog.kicad_sch" (at 50 0 0) (effects (font (size 1.27 1.27))))
|
|
)
|
|
""",
|
|
))
|
|
parse_kicad(root)
|
|
fields = kicad_part_fields(root)
|
|
assert fields["U3"]["cad_sheet"] == "analog.kicad_sch"
|
|
assert fields["R1"]["cad_sheet"] == "root.kicad_sch"
|
|
assert fields["U3"].get("cad_uuid")
|
|
assert fields["U3"]["cad_uuid"] != fields["R1"]["cad_uuid"]
|
|
|
|
|
|
def test_plugin_finds_bridge_and_pcb_target(tmp_path: Path):
|
|
(tmp_path / "periscope-findings.json").write_text(
|
|
'{"version":1,"project_id":"p","findings":[]}\n'
|
|
)
|
|
nested = tmp_path / "board"
|
|
nested.mkdir()
|
|
found = find_bridge_file(nested / "x.kicad_pcb")
|
|
assert found == tmp_path / "periscope-findings.json"
|
|
assert load_bridge(found)["version"] == 1
|
|
t = focus_target({"ref": "U1", "rule_id": "PE-PLC-001", "uuid": "x", "sheet": ""})
|
|
assert t["kind"] == "pcb" and t["ref"] == "U1"
|
|
missing = tmp_path / "nowhere"
|
|
missing.mkdir()
|
|
assert find_bridge_file(missing, max_up=0) is None
|