"""Relations builder regression tests. These run a tiny synthetic Document through Relations.build to exercise: - composite-id parsing for NET / LAYER / PAD_NET / RULE - LINE.lineGroup → WIRE indexing - ATTR.parentId resolution (direct + compound `-` form) - cross-references on partId / netName / layerId """ from tools.epro2.relations import Relations, parse_composite_id from tools.epro2.replay import Document def _doc(obj_pairs): """Build a Document with given (id, payload) entries; payload _type required.""" d = Document(doc_uuid="test", doc_type="PCB") for oid, payload in obj_pairs: d.objects[oid] = payload return d def test_parse_composite_id_basic(): assert parse_composite_id('["LAYER",1]') == ["LAYER", 1] assert parse_composite_id('["NET","+12V"]') == ["NET", "+12V"] assert parse_composite_id('["PAD_NET","e0","1","e7"]') == ["PAD_NET", "e0", "1", "e7"] assert parse_composite_id("e1") is None # plain id assert parse_composite_id("[bad]") is None # malformed JSON def test_layer_and_net_extraction(): d = _doc([ ('["LAYER",1]', {"_type": "LAYER", "layerName": "Top Layer"}), ('["LAYER",2]', {"_type": "LAYER", "layerName": "Bottom Layer"}), ('["NET","GND"]', {"_type": "NET", "netType": None}), ]) rel = Relations.build(d) assert rel.layers[1]["layerName"] == "Top Layer" assert rel.layers[2]["layerName"] == "Bottom Layer" assert "GND" in rel.nets def test_lines_grouped_by_wire(): d = _doc([ ("e637", {"_type": "WIRE", "groupId": "", "zIndex": 53}), ("ln1", {"_type": "LINE", "lineGroup": "e637", "startX": 0, "startY": 0, "endX": 10, "endY": 0}), ("ln2", {"_type": "LINE", "lineGroup": "e637", "startX": 10, "startY": 0, "endX": 10, "endY": 10}), ("ln3", {"_type": "LINE", "lineGroup": "e999", "startX": 0, "startY": 0, "endX": 1, "endY": 1}), # orphan ]) rel = Relations.build(d) assert sorted(rel.lines_by_wire["e637"]) == ["ln1", "ln2"] assert rel.lines_by_wire["e999"] == ["ln3"] assert rel.unresolved_wires == 1 # ln3's wire 'e999' doesn't exist def test_attr_parent_resolution_direct_and_compound(): d = _doc([ ("e1", {"_type": "COMPONENT", "partId": "pidABC", "x": 0, "y": 0}), ("a1", {"_type": "ATTR", "parentId": "e1", "key": "Designator", "value": "R1"}), ("a2", {"_type": "ATTR", "parentId": "e1-pin3", "key": "PinName", "value": "VCC"}), ("a3", {"_type": "ATTR", "parentId": "ghost", "key": "X", "value": "Y"}), # truly orphan ]) rel = Relations.build(d) assert sorted(rel.attrs_by_parent["e1"]) == ["a1"] assert rel.attrs_by_parent["e1-pin3"] == ["a2"] assert rel.unresolved_parents == 1 # only `ghost` is fully unresolved def test_pad_net_indexing(): d = _doc([ ('["PAD_NET","e0","1","e7"]', {"_type": "PAD_NET", "padNet": "GND"}), ('["PAD_NET","e0","2","e8"]', {"_type": "PAD_NET", "padNet": "VCC"}), ('["PAD_NET","e1","1","e7"]', {"_type": "PAD_NET", "padNet": "GND"}), # same pad, diff comp/pin ]) rel = Relations.build(d) # pad e7 is referenced by 2 PAD_NETs (different (comp,pin) pairs) assert len(rel.pad_nets_by_pad["e7"]) == 2 # net GND has 2 references; VCC has 1 assert len(rel.pad_nets_by_net["GND"]) == 2 assert len(rel.pad_nets_by_net["VCC"]) == 1 def test_attrs_dict_collapse(): d = _doc([ ("e1", {"_type": "COMPONENT", "partId": "p"}), ("a1", {"_type": "ATTR", "parentId": "e1", "key": "Designator", "value": "R1"}), ("a2", {"_type": "ATTR", "parentId": "e1", "key": "Value", "value": "10kΩ"}), ("a3", {"_type": "ATTR", "parentId": "e1", "key": "Designator", "value": "R2"}), # last write wins ]) rel = Relations.build(d) flat = rel.attrs_dict("e1") assert flat == {"Designator": "R2", "Value": "10kΩ"} def test_components_by_part_index(): d = _doc([ ("e1", {"_type": "COMPONENT", "partId": "pidA"}), ("e2", {"_type": "COMPONENT", "partId": "pidA"}), ("e3", {"_type": "COMPONENT", "partId": "pidB"}), ]) rel = Relations.build(d) assert sorted(rel.components_by_part["pidA"]) == ["e1", "e2"] assert rel.components_by_part["pidB"] == ["e3"] def test_objects_on_layer_and_in_net(): d = _doc([ ('["LAYER",1]', {"_type": "LAYER", "layerName": "Top"}), ("v1", {"_type": "VIA", "layerId": 1, "netName": "GND"}), ("v2", {"_type": "VIA", "layerId": 1, "netName": "VCC"}), ("p1", {"_type": "POLY", "layerId": 99, "netName": "GND"}), # layer 99 doesn't exist → unresolved ]) rel = Relations.build(d) assert sorted(rel.objects_on_layer[1]) == ["v1", "v2"] assert rel.unresolved_layers == 1 assert sorted(rel.objects_in_net["GND"]) == ["p1", "v1"] def test_summary_keys_present(): d = _doc([]) rel = Relations.build(d) s = rel.summary() for key in ("parts", "components", "pins", "pads", "nets", "layers", "rules", "lines_grouped", "attrs_attached", "pad_nets", "unresolved_parents", "unresolved_wires", "unresolved_layers", "bad_composite_ids"): assert key in s, f"missing summary key: {key}"