# Copyright (c) 2026 Umber # # Licensed under the Apache License, Version 2.0; see LICENSE. from __future__ import annotations import json from types import SimpleNamespace from unittest import mock import pytest from fiwicontrol.lab.discovery import ( MONSOON_USB_VID, MONSOON_USB_PIDS, _discover_monsoon_sysfs_usb, acroname_module_info_from_spec, discover_monsoon_serial_ports, discovery_json, parse_remote_discovery_payload, ) @pytest.mark.parametrize( "model_id, stem, downstream, entities", [ (19, "USBHub3p", 8, 12), (17, "USBHub2x4", 4, 6), (24, "USBHub3c", 8, 8), ], ) def test_acroname_module_info_from_spec_layout(model_id, stem, downstream, entities): pytest.importorskip("brainstem") import brainstem.discover as discover spec = discover.Spec(discover.Spec.USB, 0xABCDEF01, 6, model_id) info = acroname_module_info_from_spec(spec) assert info.stem_class == stem assert info.downstream_usb_ports == downstream assert info.hub_port_entities == entities assert info.transport == "USB" assert info.serial_number == 0xABCDEF01 assert info.module_address == 6 assert stem in info.model_name or info.model_name.startswith("USB") def test_discover_monsoon_sysfs_usb(tmp_path): dev = tmp_path / "5-2" dev.mkdir() (dev / "idVendor").write_text("2ab9\n", encoding="utf-8") (dev / "idProduct").write_text("0001\n", encoding="utf-8") (dev / "busnum").write_text("5\n", encoding="utf-8") (dev / "devnum").write_text("2\n", encoding="utf-8") (dev / "serial").write_text("HVPM-TEST\n", encoding="utf-8") rows = _discover_monsoon_sysfs_usb( vid=MONSOON_USB_VID, pids=MONSOON_USB_PIDS, sysfs_usb_devices=tmp_path, ) assert len(rows) == 1 assert rows[0].device == "/dev/bus/usb/5/2" assert rows[0].vid == MONSOON_USB_VID assert rows[0].pid == 0x0001 assert rows[0].serial_number == "HVPM-TEST" def test_discover_monsoon_serial_ports_filters_vid_pid(): pytest.importorskip("serial") fake = SimpleNamespace( device="/dev/ttyUSB0", serial_number="SN1", vid=MONSOON_USB_VID, pid=0x0001, manufacturer="Monsoon", description="HVPM", hwid="USB VID:PID=2AB9:0001", ) other = SimpleNamespace( device="/dev/ttyS0", serial_number=None, vid=0x1234, pid=0x5678, manufacturer=None, description="not monsoon", hwid="", ) with mock.patch("serial.tools.list_ports.comports", return_value=[other, fake]): ports = discover_monsoon_serial_ports() assert len(ports) == 1 assert ports[0].device == "/dev/ttyUSB0" assert ports[0].serial_number == "SN1" def test_discovery_json_is_parseable(): pytest.importorskip("brainstem") pytest.importorskip("serial") with mock.patch("fiwicontrol.lab.discovery.discover_acroname_modules", return_value=[]): with mock.patch("fiwicontrol.lab.discovery.discover_monsoon_serial_ports", return_value=[]): data = json.loads(discovery_json()) assert data["acroname"] == [] assert data["monsoon"] == [] assert "brainstem_version" in data def test_parse_remote_discovery_payload_round_trip(): pytest.importorskip("brainstem") import brainstem.discover as discover spec = discover.Spec(discover.Spec.USB, 1, 6, 17) info = acroname_module_info_from_spec(spec) raw = { "acroname": [ { "transport": info.transport, "serial_number": info.serial_number, "module_address": info.module_address, "model_id": info.model_id, "model_name": info.model_name, "model_description": info.model_description, "stem_class": info.stem_class, "downstream_usb_ports": info.downstream_usb_ports, "hub_port_entities": info.hub_port_entities, } ], "monsoon": [], } ac, mo = parse_remote_discovery_payload(raw) assert len(ac) == 1 assert ac[0].stem_class == "USBHub2x4" assert ac[0].downstream_usb_ports == 4 assert mo == []