From 673519f6bff1c5638aa81f0861124e4537993e25 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 12 Sep 2021 12:07:40 -1000 Subject: [PATCH] Prefer more targeted matchers in USB discovery (#56142) - If there is a more targeted match it should win discovery --- homeassistant/components/usb/__init__.py | 15 +++++++++ tests/components/usb/test_init.py | 39 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/homeassistant/components/usb/__init__.py b/homeassistant/components/usb/__init__.py index 13f18216cca..095d72f3ed4 100644 --- a/homeassistant/components/usb/__init__.py +++ b/homeassistant/components/usb/__init__.py @@ -161,6 +161,7 @@ class USBDiscovery: if device_tuple in self.seen: return self.seen.add(device_tuple) + matched = [] for matcher in self.usb: if "vid" in matcher and device.vid != matcher["vid"]: continue @@ -178,6 +179,20 @@ class USBDiscovery: device.description, matcher["description"] ): continue + matched.append(matcher) + + if not matched: + return + + sorted_by_most_targeted = sorted(matched, key=lambda item: -len(item)) + most_matched_fields = len(sorted_by_most_targeted[0]) + + for matcher in sorted_by_most_targeted: + # If there is a less targeted match, we only + # want the most targeted match + if len(matcher) < most_matched_fields: + break + flow: USBFlow = { "domain": matcher["domain"], "context": {"source": config_entries.SOURCE_USB}, diff --git a/tests/components/usb/test_init.py b/tests/components/usb/test_init.py index b09dad9ebe4..7d620b45984 100644 --- a/tests/components/usb/test_init.py +++ b/tests/components/usb/test_init.py @@ -176,6 +176,45 @@ async def test_discovered_by_websocket_scan_limited_by_description_matcher( assert mock_config_flow.mock_calls[0][1][0] == "test1" +async def test_most_targeted_matcher_wins(hass, hass_ws_client): + """Test that the most targeted matcher is used.""" + new_usb = [ + {"domain": "less", "vid": "3039", "pid": "3039"}, + {"domain": "more", "vid": "3039", "pid": "3039", "description": "*2652*"}, + ] + + mock_comports = [ + MagicMock( + device=slae_sh_device.device, + vid=12345, + pid=12345, + serial_number=slae_sh_device.serial_number, + manufacturer=slae_sh_device.manufacturer, + description=slae_sh_device.description, + ) + ] + + with patch("pyudev.Context", side_effect=ImportError), patch( + "homeassistant.components.usb.async_get_usb", return_value=new_usb + ), patch( + "homeassistant.components.usb.comports", return_value=mock_comports + ), patch.object( + hass.config_entries.flow, "async_init" + ) as mock_config_flow: + assert await async_setup_component(hass, "usb", {"usb": {}}) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + ws_client = await hass_ws_client(hass) + await ws_client.send_json({"id": 1, "type": "usb/scan"}) + response = await ws_client.receive_json() + assert response["success"] + await hass.async_block_till_done() + + assert len(mock_config_flow.mock_calls) == 1 + assert mock_config_flow.mock_calls[0][1][0] == "more" + + async def test_discovered_by_websocket_scan_rejected_by_description_matcher( hass, hass_ws_client ):