"""M0 protocol pack: parse, reject typical-as-standard, no invented ohms.""" from __future__ import annotations import copy import json import pytest from backend.periscopex.models import ValidationReport from backend.periscopex.protocol_catalog import ( CATALOG_PATH, SCHEMA_PATH, SCHEMA_VERSION, ProtocolCatalogError, build_m0_catalog, empty_protocol_section, fail_mandatory, load_catalog, map_protocol_outcome, parse_catalog, ) REQUIRED_LOGICAL = { "usb2-ls-fs", "usb2-ls", "usb2-fs", "usb2-hs", "usb-c", "usb-c-usb2", "usb3-x", "usb4", "100base-tx", "10base-t", "1000base-t", "hdmi-1.4", "hdmi-2.0", "hdmi-frl", "dvi", "ddr-family", "ddr4", "lpddr-family", "lpddr4", "hbm-family", "hbm3", "hyperbus", "qspi", "octal-spi", "emmc", "ufs", "axi4", "axi4-lite", "ahb", "apb", "wishbone", "avalon-mm", "avalon-st", "tilelink", "pcie", "cxl", "pci", "pci-x", "ccix", "opencapi", "upi", "dmi", "infinity-fabric", "hypertransport", "ucie", "i2c", "can", "rgmii", } def test_committed_catalog_parses_and_matches_builder(): loaded = load_catalog() rebuilt = parse_catalog(build_m0_catalog()) assert loaded.schema_version == SCHEMA_VERSION assert loaded.model_dump() == rebuilt.model_dump() assert CATALOG_PATH.is_file() assert SCHEMA_PATH.is_file() schema = json.loads(SCHEMA_PATH.read_text(encoding="utf-8")) assert "properties" in schema assert schema["properties"]["schema_version"] def test_logical_and_physical_ids_are_disjoint(): cat = load_catalog() logical = {p.id for p in cat.logical_protocols} physical = {p.id for p in cat.physical_interfaces} assert not (logical & physical) assert REQUIRED_LOGICAL <= logical for iface in cat.physical_interfaces: assert iface.id != iface.logical_protocol_id assert iface.pcb_relevant in {"YES", "NO", "CONDITIONAL"} def test_amba_internal_is_not_pcb_routing(): cat = load_catalog() axi = next(p for p in cat.physical_interfaces if p.id == "axi4-internal") assert axi.pcb_relevant == "NO" assert axi.physical_layer == "INTERNAL" assert axi.logical_protocol_id == "axi4" assert axi.constraints[0].value_kind == "NOT_APPLICABLE" def test_ddr_has_no_universal_mm_or_ps(): cat = load_catalog() ddr4 = next(p for p in cat.physical_interfaces if p.id == "ddr4-sdram-pcb") assert ddr4.pcb_relevant == "YES" for c in ddr4.constraints: assert c.value_kind in { "CONTROLLER_DEPENDENT", "PHY_DEPENDENT", "UNKNOWN", "VENDOR_DEPENDENT", "MISSING_SOURCE", "NOT_APPLICABLE", } assert c.value is None assert c.unit is None def test_hbm_dq_not_pcb_relevant(): cat = load_catalog() hbm = next(p for p in cat.physical_interfaces if p.id == "hbm3-package") assert hbm.pcb_relevant == "NO" def test_hdmi_mini_is_same_logical_different_connector(): cat = load_catalog() a = next(p for p in cat.physical_interfaces if p.id == "hdmi-1.4-tmds-type-a") mini = next( p for p in cat.physical_interfaces if p.id == "hdmi-1.4-tmds-type-c-mini" ) assert a.logical_protocol_id == mini.logical_protocol_id == "hdmi-1.4" assert a.connector == "HDMI-A" assert mini.connector == "HDMI-C-mini" def test_catalog_numeric_requires_official_cite_no_invented_usb_90ohm(): cat = load_catalog() usb_z = [] for iface in cat.physical_interfaces: for c in iface.constraints: if c.value_kind == "NUMERIC": assert c.value is not None assert c.source is not None assert c.source.document and c.source.organization assert c.source.revision assert c.source.page assert c.source.section or c.source.table if ( iface.logical_protocol_id.startswith("usb") and c.parameter == "differential_impedance" ): usb_z.append(c) assert c.value_kind != "NUMERIC" assert c.value is None assert usb_z blob = json.dumps([c.model_dump() for c in usb_z]) assert "90" not in blob def test_recommended_is_not_fail_mandatory(): cat = load_catalog() recs = [ c for iface in cat.physical_interfaces for c in iface.constraints if c.mandatory == "RECOMMENDED" ] assert recs, "catalog should include at least one RECOMMENDED skeleton" for c in recs: assert fail_mandatory(c) is False cls, status, _ = map_protocol_outcome("FAIL", c.mandatory) assert status != "ERROR" assert cls != "RULE" def test_mandatory_fail_can_be_error(): cls, status, _ = map_protocol_outcome("FAIL", "MANDATORY") assert cls == "RULE" assert status == "ERROR" def test_reject_typical_as_standard(): raw = build_m0_catalog() iface = raw["physical_interfaces"][0] typical = copy.deepcopy(iface["constraints"][0]) typical["id"] = "bad-typical" typical["typical"] = True typical["value_origin"] = "TYPICAL" typical["source_type"] = "STANDARD" typical["source_class"] = "NORMATIVE" typical["mandatory"] = "MANDATORY" typical["value_kind"] = "UNKNOWN" typical["value"] = None iface = copy.deepcopy(iface) iface["id"] = "usb2-hs-typical-bad" iface["constraints"] = [typical] raw["physical_interfaces"].append(iface) with pytest.raises(ProtocolCatalogError, match="typical"): parse_catalog(raw) def test_reject_constraint_without_source_type(): raw = build_m0_catalog() iface = copy.deepcopy(raw["physical_interfaces"][0]) iface["id"] = "usb2-hs-no-source" bad = copy.deepcopy(iface["constraints"][0]) bad["id"] = "no-source" del bad["source_type"] iface["constraints"] = [bad] raw["physical_interfaces"].append(iface) with pytest.raises(ProtocolCatalogError): parse_catalog(raw) def test_reject_numeric_without_cite(): raw = build_m0_catalog() iface = copy.deepcopy(raw["physical_interfaces"][0]) iface["id"] = "usb2-hs-fake-z" iface["constraints"] = [{ "id": "fake-zdiff", "parameter": "differential_impedance", "value_kind": "NUMERIC", "value": 90.0, "unit": "ohm", "mandatory": "MANDATORY", "source_type": "STANDARD", "source_class": "NORMATIVE", "source": None, "typical": False, "value_origin": "SPEC", "conditions": [], }] raw["physical_interfaces"].append(iface) with pytest.raises(ProtocolCatalogError, match="cite"): parse_catalog(raw) def test_reject_boolean_pcb_relevant(): raw = build_m0_catalog() iface = copy.deepcopy(raw["physical_interfaces"][0]) iface["id"] = "usb2-hs-bool-pcb" iface["pcb_relevant"] = True raw["physical_interfaces"].append(iface) with pytest.raises(ProtocolCatalogError): parse_catalog(raw) def test_reject_same_logical_and_physical_id(): raw = build_m0_catalog() iface = copy.deepcopy(raw["physical_interfaces"][0]) iface["id"] = iface["logical_protocol_id"] raw["physical_interfaces"].append(iface) with pytest.raises(ProtocolCatalogError, match="differ"): parse_catalog(raw) def test_empty_report_section_has_no_z(): section = empty_protocol_section() dumped = section.model_dump() assert section.message == "nessun protocollo riconosciuto" assert section.recognized_instances == [] assert dumped.get("max_level_reached") is None blob = json.dumps(dumped) assert "90" not in blob assert "ohm" not in blob.lower() assert "Zdiff" not in blob assert "z0" not in blob.lower() report = ValidationReport( project="t", timestamp="2026-09-22T00:00:00Z", findings=[], summary={"ERROR": 0, "WARNING": 0, "INFO": 0}, protocol_certification=dumped, ) assert report.protocol_certification["message"] == "nessun protocollo riconosciuto" def test_pcie_cxl_physical_packs_are_empty_numbers(): cat = load_catalog() for pid in ("pcie-phy-pcb", "cxl-phy-pcb"): iface = next(p for p in cat.physical_interfaces if p.id == pid) assert iface.logical_protocol_id in {"pcie", "cxl"} assert iface.required_checks for c in iface.constraints: assert c.value_kind in {"MISSING_SOURCE", "UNKNOWN"} assert c.value is None