|
|
|
@ -7,6 +7,7 @@ import os
|
|
|
|
|
from typing import Any
|
|
|
|
|
from unittest.mock import MagicMock, Mock, call, patch, sentinel
|
|
|
|
|
|
|
|
|
|
from aiousbwatcher import InotifyNotAvailableError
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from homeassistant.components import usb
|
|
|
|
@ -15,58 +16,29 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT
|
|
|
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
|
from homeassistant.helpers.service_info.usb import UsbServiceInfo
|
|
|
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
|
from homeassistant.util import dt as dt_util
|
|
|
|
|
|
|
|
|
|
from . import conbee_device, slae_sh_device
|
|
|
|
|
|
|
|
|
|
from tests.common import import_and_test_deprecated_constant
|
|
|
|
|
from tests.common import async_fire_time_changed, import_and_test_deprecated_constant
|
|
|
|
|
from tests.typing import WebSocketGenerator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(name="operating_system")
|
|
|
|
|
def mock_operating_system():
|
|
|
|
|
"""Mock running Home Assistant Operating system."""
|
|
|
|
|
@pytest.fixture(name="aiousbwatcher_no_inotify")
|
|
|
|
|
def aiousbwatcher_no_inotify():
|
|
|
|
|
"""Patch AIOUSBWatcher to not use inotify."""
|
|
|
|
|
with patch(
|
|
|
|
|
"homeassistant.components.usb.system_info.async_get_system_info",
|
|
|
|
|
return_value={
|
|
|
|
|
"hassio": True,
|
|
|
|
|
"docker": True,
|
|
|
|
|
},
|
|
|
|
|
"homeassistant.components.usb.AIOUSBWatcher.async_start",
|
|
|
|
|
side_effect=InotifyNotAvailableError,
|
|
|
|
|
):
|
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(name="docker")
|
|
|
|
|
def mock_docker():
|
|
|
|
|
"""Mock running Home Assistant in docker container."""
|
|
|
|
|
with patch(
|
|
|
|
|
"homeassistant.components.usb.system_info.async_get_system_info",
|
|
|
|
|
return_value={
|
|
|
|
|
"hassio": False,
|
|
|
|
|
"docker": True,
|
|
|
|
|
},
|
|
|
|
|
):
|
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(name="venv")
|
|
|
|
|
def mock_venv():
|
|
|
|
|
"""Mock running Home Assistant in a venv container."""
|
|
|
|
|
with patch(
|
|
|
|
|
"homeassistant.components.usb.system_info.async_get_system_info",
|
|
|
|
|
return_value={
|
|
|
|
|
"hassio": False,
|
|
|
|
|
"docker": False,
|
|
|
|
|
"virtualenv": True,
|
|
|
|
|
},
|
|
|
|
|
):
|
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_observer_discovery(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator, venv
|
|
|
|
|
async def test_aiousbwatcher_discovery(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Test that observer can discover a device without raising an exception."""
|
|
|
|
|
new_usb = [{"domain": "test1", "vid": "3039"}]
|
|
|
|
|
"""Test that aiousbwatcher can discover a device without raising an exception."""
|
|
|
|
|
new_usb = [{"domain": "test1", "vid": "3039"}, {"domain": "test2", "vid": "0FA0"}]
|
|
|
|
|
|
|
|
|
|
mock_comports = [
|
|
|
|
|
MagicMock(
|
|
|
|
@ -78,26 +50,23 @@ async def test_observer_discovery(
|
|
|
|
|
description=slae_sh_device.description,
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
mock_observer = None
|
|
|
|
|
|
|
|
|
|
async def _mock_monitor_observer_callback(callback):
|
|
|
|
|
await hass.async_add_executor_job(
|
|
|
|
|
callback, MagicMock(action="add", device_path="/dev/new")
|
|
|
|
|
)
|
|
|
|
|
aiousbwatcher_callback = None
|
|
|
|
|
|
|
|
|
|
def _create_mock_monitor_observer(monitor, callback, name):
|
|
|
|
|
nonlocal mock_observer
|
|
|
|
|
hass.create_task(_mock_monitor_observer_callback(callback))
|
|
|
|
|
mock_observer = MagicMock()
|
|
|
|
|
return mock_observer
|
|
|
|
|
def async_register_callback(callback):
|
|
|
|
|
nonlocal aiousbwatcher_callback
|
|
|
|
|
aiousbwatcher_callback = callback
|
|
|
|
|
|
|
|
|
|
MockAIOUSBWatcher = MagicMock()
|
|
|
|
|
MockAIOUSBWatcher.async_register_callback = async_register_callback
|
|
|
|
|
|
|
|
|
|
with (
|
|
|
|
|
patch("sys.platform", "linux"),
|
|
|
|
|
patch("pyudev.Context"),
|
|
|
|
|
patch("pyudev.MonitorObserver", new=_create_mock_monitor_observer),
|
|
|
|
|
patch("pyudev.Monitor.filter_by"),
|
|
|
|
|
patch("homeassistant.components.usb.async_get_usb", return_value=new_usb),
|
|
|
|
|
patch("homeassistant.components.usb.comports", return_value=mock_comports),
|
|
|
|
|
patch(
|
|
|
|
|
"homeassistant.components.usb.AIOUSBWatcher", return_value=MockAIOUSBWatcher
|
|
|
|
|
),
|
|
|
|
|
patch.object(hass.config_entries.flow, "async_init") as mock_config_flow,
|
|
|
|
|
):
|
|
|
|
|
assert await async_setup_component(hass, "usb", {"usb": {}})
|
|
|
|
@ -105,18 +74,42 @@ async def test_observer_discovery(
|
|
|
|
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 1
|
|
|
|
|
assert mock_config_flow.mock_calls[0][1][0] == "test1"
|
|
|
|
|
assert aiousbwatcher_callback is not None
|
|
|
|
|
|
|
|
|
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 1
|
|
|
|
|
assert mock_config_flow.mock_calls[0][1][0] == "test1"
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 1
|
|
|
|
|
|
|
|
|
|
# pylint:disable-next=unnecessary-dunder-call
|
|
|
|
|
assert mock_observer.mock_calls == [call.start(), call.__bool__(), call.stop()]
|
|
|
|
|
mock_comports.append(
|
|
|
|
|
MagicMock(
|
|
|
|
|
device=slae_sh_device.device,
|
|
|
|
|
vid=4000,
|
|
|
|
|
pid=4000,
|
|
|
|
|
serial_number=slae_sh_device.serial_number,
|
|
|
|
|
manufacturer=slae_sh_device.manufacturer,
|
|
|
|
|
description=slae_sh_device.description,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
aiousbwatcher_callback()
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
|
|
async_fire_time_changed(
|
|
|
|
|
hass, dt_util.utcnow() + timedelta(seconds=usb.ADD_REMOVE_SCAN_COOLDOWN)
|
|
|
|
|
)
|
|
|
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
|
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 2
|
|
|
|
|
assert mock_config_flow.mock_calls[1][1][0] == "test2"
|
|
|
|
|
|
|
|
|
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_polling_discovery(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator, venv
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Test that polling can discover a device without raising an exception."""
|
|
|
|
|
new_usb = [{"domain": "test1", "vid": "3039"}]
|
|
|
|
@ -143,10 +136,6 @@ async def test_polling_discovery(
|
|
|
|
|
|
|
|
|
|
with (
|
|
|
|
|
patch("sys.platform", "linux"),
|
|
|
|
|
patch(
|
|
|
|
|
"homeassistant.components.usb.USBDiscovery._get_monitor_observer",
|
|
|
|
|
return_value=None,
|
|
|
|
|
),
|
|
|
|
|
patch(
|
|
|
|
|
"homeassistant.components.usb.POLLING_MONITOR_SCAN_PERIOD",
|
|
|
|
|
timedelta(seconds=0.01),
|
|
|
|
@ -174,19 +163,9 @@ async def test_polling_discovery(
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_removal_by_observer_before_started(
|
|
|
|
|
hass: HomeAssistant, operating_system
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Test a device is removed by the observer before started."""
|
|
|
|
|
|
|
|
|
|
async def _mock_monitor_observer_callback(callback):
|
|
|
|
|
await hass.async_add_executor_job(
|
|
|
|
|
callback, MagicMock(action="remove", device_path="/dev/new")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _create_mock_monitor_observer(monitor, callback, name):
|
|
|
|
|
hass.async_create_task(_mock_monitor_observer_callback(callback))
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_removal_by_aiousbwatcher_before_started(hass: HomeAssistant) -> None:
|
|
|
|
|
"""Test a device is removed by the aiousbwatcher before started."""
|
|
|
|
|
new_usb = [{"domain": "test1", "vid": "3039", "pid": "3039"}]
|
|
|
|
|
|
|
|
|
|
mock_comports = [
|
|
|
|
@ -203,7 +182,6 @@ async def test_removal_by_observer_before_started(
|
|
|
|
|
with (
|
|
|
|
|
patch("homeassistant.components.usb.async_get_usb", return_value=new_usb),
|
|
|
|
|
patch("homeassistant.components.usb.comports", return_value=mock_comports),
|
|
|
|
|
patch("pyudev.MonitorObserver", new=_create_mock_monitor_observer),
|
|
|
|
|
patch.object(hass.config_entries.flow, "async_init") as mock_config_flow,
|
|
|
|
|
):
|
|
|
|
|
assert await async_setup_component(hass, "usb", {"usb": {}})
|
|
|
|
@ -219,6 +197,7 @@ async def test_removal_by_observer_before_started(
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_discovered_by_websocket_scan(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -237,7 +216,6 @@ async def test_discovered_by_websocket_scan(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
@ -256,6 +234,7 @@ async def test_discovered_by_websocket_scan(
|
|
|
|
|
assert mock_config_flow.mock_calls[0][1][0] == "test1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_discovered_by_websocket_scan_limited_by_description_matcher(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -276,7 +255,6 @@ async def test_discovered_by_websocket_scan_limited_by_description_matcher(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
@ -295,6 +273,7 @@ async def test_discovered_by_websocket_scan_limited_by_description_matcher(
|
|
|
|
|
assert mock_config_flow.mock_calls[0][1][0] == "test1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_most_targeted_matcher_wins(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -316,7 +295,6 @@ async def test_most_targeted_matcher_wins(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
@ -335,6 +313,7 @@ async def test_most_targeted_matcher_wins(
|
|
|
|
|
assert mock_config_flow.mock_calls[0][1][0] == "more"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_discovered_by_websocket_scan_rejected_by_description_matcher(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -355,7 +334,6 @@ async def test_discovered_by_websocket_scan_rejected_by_description_matcher(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
@ -373,6 +351,7 @@ async def test_discovered_by_websocket_scan_rejected_by_description_matcher(
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_discovered_by_websocket_scan_limited_by_serial_number_matcher(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -398,7 +377,6 @@ async def test_discovered_by_websocket_scan_limited_by_serial_number_matcher(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
@ -417,6 +395,7 @@ async def test_discovered_by_websocket_scan_limited_by_serial_number_matcher(
|
|
|
|
|
assert mock_config_flow.mock_calls[0][1][0] == "test1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_discovered_by_websocket_scan_rejected_by_serial_number_matcher(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -437,7 +416,6 @@ async def test_discovered_by_websocket_scan_rejected_by_serial_number_matcher(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
@ -455,6 +433,7 @@ async def test_discovered_by_websocket_scan_rejected_by_serial_number_matcher(
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_discovered_by_websocket_scan_limited_by_manufacturer_matcher(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -480,7 +459,6 @@ async def test_discovered_by_websocket_scan_limited_by_manufacturer_matcher(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
@ -499,6 +477,7 @@ async def test_discovered_by_websocket_scan_limited_by_manufacturer_matcher(
|
|
|
|
|
assert mock_config_flow.mock_calls[0][1][0] == "test1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_discovered_by_websocket_scan_rejected_by_manufacturer_matcher(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -524,7 +503,6 @@ async def test_discovered_by_websocket_scan_rejected_by_manufacturer_matcher(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
@ -542,6 +520,7 @@ async def test_discovered_by_websocket_scan_rejected_by_manufacturer_matcher(
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_discovered_by_websocket_rejected_with_empty_serial_number_only(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -562,7 +541,6 @@ async def test_discovered_by_websocket_rejected_with_empty_serial_number_only(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
@ -580,6 +558,7 @@ async def test_discovered_by_websocket_rejected_with_empty_serial_number_only(
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_discovered_by_websocket_scan_match_vid_only(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -598,7 +577,6 @@ async def test_discovered_by_websocket_scan_match_vid_only(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
@ -617,6 +595,7 @@ async def test_discovered_by_websocket_scan_match_vid_only(
|
|
|
|
|
assert mock_config_flow.mock_calls[0][1][0] == "test1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_discovered_by_websocket_scan_match_vid_wrong_pid(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -635,7 +614,6 @@ async def test_discovered_by_websocket_scan_match_vid_wrong_pid(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
@ -653,6 +631,7 @@ async def test_discovered_by_websocket_scan_match_vid_wrong_pid(
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_discovered_by_websocket_no_vid_pid(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -671,7 +650,6 @@ async def test_discovered_by_websocket_no_vid_pid(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
@ -689,9 +667,9 @@ async def test_discovered_by_websocket_no_vid_pid(
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("exception_type", [ImportError, OSError])
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_non_matching_discovered_by_scanner_after_started(
|
|
|
|
|
hass: HomeAssistant, exception_type, hass_ws_client: WebSocketGenerator
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Test a websocket scan that does not match."""
|
|
|
|
|
new_usb = [{"domain": "test1", "vid": "4444", "pid": "4444"}]
|
|
|
|
@ -708,7 +686,6 @@ async def test_non_matching_discovered_by_scanner_after_started(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
with (
|
|
|
|
|
patch("pyudev.Context", side_effect=exception_type),
|
|
|
|
|
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,
|
|
|
|
@ -726,10 +703,10 @@ async def test_non_matching_discovered_by_scanner_after_started(
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_observer_on_wsl_fallback_without_throwing_exception(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator, venv
|
|
|
|
|
async def test_aiousbwatcher_on_wsl_fallback_without_throwing_exception(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Test that observer on WSL failure results in fallback to scanning without raising an exception."""
|
|
|
|
|
"""Test that aiousbwatcher on WSL failure results in fallback to scanning without raising an exception."""
|
|
|
|
|
new_usb = [{"domain": "test1", "vid": "3039"}]
|
|
|
|
|
|
|
|
|
|
mock_comports = [
|
|
|
|
@ -744,8 +721,6 @@ async def test_observer_on_wsl_fallback_without_throwing_exception(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
with (
|
|
|
|
|
patch("pyudev.Context"),
|
|
|
|
|
patch("pyudev.Monitor.filter_by", side_effect=ValueError),
|
|
|
|
|
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,
|
|
|
|
@ -764,20 +739,8 @@ async def test_observer_on_wsl_fallback_without_throwing_exception(
|
|
|
|
|
assert mock_config_flow.mock_calls[0][1][0] == "test1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_not_discovered_by_observer_before_started_on_docker(
|
|
|
|
|
hass: HomeAssistant, docker
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Test a device is not discovered since observer is not running on bare docker."""
|
|
|
|
|
|
|
|
|
|
async def _mock_monitor_observer_callback(callback):
|
|
|
|
|
await hass.async_add_executor_job(
|
|
|
|
|
callback, MagicMock(action="add", device_path="/dev/new")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _create_mock_monitor_observer(monitor, callback, name):
|
|
|
|
|
hass.async_create_task(_mock_monitor_observer_callback(callback))
|
|
|
|
|
return MagicMock()
|
|
|
|
|
|
|
|
|
|
async def test_discovered_by_aiousbwatcher_before_started(hass: HomeAssistant) -> None:
|
|
|
|
|
"""Test a device is discovered since aiousbwatcher is now running."""
|
|
|
|
|
new_usb = [{"domain": "test1", "vid": "3039", "pid": "3039"}]
|
|
|
|
|
|
|
|
|
|
mock_comports = [
|
|
|
|
@ -790,23 +753,45 @@ async def test_not_discovered_by_observer_before_started_on_docker(
|
|
|
|
|
description=slae_sh_device.description,
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
initial_mock_comports = []
|
|
|
|
|
aiousbwatcher_callback = None
|
|
|
|
|
|
|
|
|
|
def async_register_callback(callback):
|
|
|
|
|
nonlocal aiousbwatcher_callback
|
|
|
|
|
aiousbwatcher_callback = callback
|
|
|
|
|
|
|
|
|
|
MockAIOUSBWatcher = MagicMock()
|
|
|
|
|
MockAIOUSBWatcher.async_register_callback = async_register_callback
|
|
|
|
|
|
|
|
|
|
with (
|
|
|
|
|
patch("sys.platform", "linux"),
|
|
|
|
|
patch("homeassistant.components.usb.async_get_usb", return_value=new_usb),
|
|
|
|
|
patch("homeassistant.components.usb.comports", return_value=mock_comports),
|
|
|
|
|
patch("pyudev.MonitorObserver", new=_create_mock_monitor_observer),
|
|
|
|
|
patch(
|
|
|
|
|
"homeassistant.components.usb.comports", return_value=initial_mock_comports
|
|
|
|
|
),
|
|
|
|
|
patch(
|
|
|
|
|
"homeassistant.components.usb.AIOUSBWatcher", return_value=MockAIOUSBWatcher
|
|
|
|
|
),
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
with (
|
|
|
|
|
patch("homeassistant.components.usb.comports", return_value=[]),
|
|
|
|
|
patch.object(hass.config_entries.flow, "async_init") as mock_config_flow,
|
|
|
|
|
):
|
|
|
|
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 0
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 0
|
|
|
|
|
|
|
|
|
|
initial_mock_comports.extend(mock_comports)
|
|
|
|
|
aiousbwatcher_callback()
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
|
|
async_fire_time_changed(
|
|
|
|
|
hass, dt_util.utcnow() + timedelta(seconds=usb.ADD_REMOVE_SCAN_COOLDOWN)
|
|
|
|
|
)
|
|
|
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
|
|
|
|
|
|
assert len(mock_config_flow.mock_calls) == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_serial_by_id_no_dir() -> None:
|
|
|
|
@ -889,6 +874,7 @@ def test_human_readable_device_name() -> None:
|
|
|
|
|
assert "8A2A" in name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_async_is_plugged_in(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -912,7 +898,6 @@ async def test_async_is_plugged_in(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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=[]),
|
|
|
|
|
patch.object(hass.config_entries.flow, "async_init"),
|
|
|
|
@ -935,6 +920,7 @@ async def test_async_is_plugged_in(
|
|
|
|
|
assert usb.async_is_plugged_in(hass, matcher)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"matcher",
|
|
|
|
|
[
|
|
|
|
@ -953,7 +939,6 @@ async def test_async_is_plugged_in_case_enforcement(
|
|
|
|
|
new_usb = [{"domain": "test1", "vid": "ABCD"}]
|
|
|
|
|
|
|
|
|
|
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=[]),
|
|
|
|
|
patch.object(hass.config_entries.flow, "async_init"),
|
|
|
|
@ -967,6 +952,7 @@ async def test_async_is_plugged_in_case_enforcement(
|
|
|
|
|
usb.async_is_plugged_in(hass, matcher)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_web_socket_triggers_discovery_request_callbacks(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -974,7 +960,6 @@ async def test_web_socket_triggers_discovery_request_callbacks(
|
|
|
|
|
mock_callback = Mock()
|
|
|
|
|
|
|
|
|
|
with (
|
|
|
|
|
patch("pyudev.Context", side_effect=ImportError),
|
|
|
|
|
patch("homeassistant.components.usb.async_get_usb", return_value=[]),
|
|
|
|
|
patch("homeassistant.components.usb.comports", return_value=[]),
|
|
|
|
|
patch.object(hass.config_entries.flow, "async_init"),
|
|
|
|
@ -1002,6 +987,7 @@ async def test_web_socket_triggers_discovery_request_callbacks(
|
|
|
|
|
assert len(mock_callback.mock_calls) == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_initial_scan_callback(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -1010,7 +996,6 @@ async def test_initial_scan_callback(
|
|
|
|
|
mock_callback_2 = Mock()
|
|
|
|
|
|
|
|
|
|
with (
|
|
|
|
|
patch("pyudev.Context", side_effect=ImportError),
|
|
|
|
|
patch("homeassistant.components.usb.async_get_usb", return_value=[]),
|
|
|
|
|
patch("homeassistant.components.usb.comports", return_value=[]),
|
|
|
|
|
patch.object(hass.config_entries.flow, "async_init"),
|
|
|
|
@ -1038,6 +1023,7 @@ async def test_initial_scan_callback(
|
|
|
|
|
cancel_2()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_cancel_initial_scan_callback(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -1045,7 +1031,6 @@ async def test_cancel_initial_scan_callback(
|
|
|
|
|
mock_callback = Mock()
|
|
|
|
|
|
|
|
|
|
with (
|
|
|
|
|
patch("pyudev.Context", side_effect=ImportError),
|
|
|
|
|
patch("homeassistant.components.usb.async_get_usb", return_value=[]),
|
|
|
|
|
patch("homeassistant.components.usb.comports", return_value=[]),
|
|
|
|
|
patch.object(hass.config_entries.flow, "async_init"),
|
|
|
|
@ -1064,6 +1049,7 @@ async def test_cancel_initial_scan_callback(
|
|
|
|
|
assert len(mock_callback.mock_calls) == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
async def test_resolve_serial_by_id(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
|
) -> None:
|
|
|
|
@ -1082,7 +1068,6 @@ async def test_resolve_serial_by_id(
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
@ -1106,6 +1091,7 @@ async def test_resolve_serial_by_id(
|
|
|
|
|
assert mock_config_flow.mock_calls[0][2]["data"].device == "/dev/serial/by-id/bla"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"ports",
|
|
|
|
|
[
|
|
|
|
@ -1190,7 +1176,6 @@ async def test_cp2102n_ordering_on_macos(
|
|
|
|
|
|
|
|
|
|
with (
|
|
|
|
|
patch("sys.platform", "darwin"),
|
|
|
|
|
patch("pyudev.Context", side_effect=ImportError),
|
|
|
|
|
patch("homeassistant.components.usb.async_get_usb", return_value=new_usb),
|
|
|
|
|
patch("homeassistant.components.usb.comports", return_value=ports),
|
|
|
|
|
patch.object(hass.config_entries.flow, "async_init") as mock_config_flow,
|
|
|
|
@ -1239,6 +1224,7 @@ def test_deprecated_constants(
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
@patch("homeassistant.components.usb.REQUEST_SCAN_COOLDOWN", 0)
|
|
|
|
|
async def test_register_port_event_callback(
|
|
|
|
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
|
|
@ -1273,7 +1259,6 @@ async def test_register_port_event_callback(
|
|
|
|
|
|
|
|
|
|
# Start off with no ports
|
|
|
|
|
with (
|
|
|
|
|
patch("pyudev.Context", side_effect=ImportError),
|
|
|
|
|
patch("homeassistant.components.usb.comports", return_value=[]),
|
|
|
|
|
):
|
|
|
|
|
assert await async_setup_component(hass, "usb", {"usb": {}})
|
|
|
|
@ -1335,6 +1320,7 @@ async def test_register_port_event_callback(
|
|
|
|
|
assert mock_callback2.mock_calls == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("aiousbwatcher_no_inotify")
|
|
|
|
|
@patch("homeassistant.components.usb.REQUEST_SCAN_COOLDOWN", 0)
|
|
|
|
|
async def test_register_port_event_callback_failure(
|
|
|
|
|
hass: HomeAssistant,
|
|
|
|
@ -1371,7 +1357,6 @@ async def test_register_port_event_callback_failure(
|
|
|
|
|
|
|
|
|
|
# Start off with no ports
|
|
|
|
|
with (
|
|
|
|
|
patch("pyudev.Context", side_effect=ImportError),
|
|
|
|
|
patch("homeassistant.components.usb.comports", return_value=[]),
|
|
|
|
|
):
|
|
|
|
|
assert await async_setup_component(hass, "usb", {"usb": {}})
|
|
|
|
|