mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
parent
d6ec8a4a96
commit
d6097573f5
@ -36,6 +36,21 @@ DEFAULT_HOST = "1.2.3.4"
|
|||||||
DEFAULT_PORT = 1234
|
DEFAULT_PORT = 1234
|
||||||
DEFAULT_SITE = "site_id"
|
DEFAULT_SITE = "site_id"
|
||||||
|
|
||||||
|
CONTROLLER_HOST = {
|
||||||
|
"hostname": "controller_host",
|
||||||
|
"ip": DEFAULT_HOST,
|
||||||
|
"is_wired": True,
|
||||||
|
"last_seen": 1562600145,
|
||||||
|
"mac": "10:00:00:00:00:01",
|
||||||
|
"name": "Controller host",
|
||||||
|
"oui": "Producer",
|
||||||
|
"sw_mac": "00:00:00:00:01:01",
|
||||||
|
"sw_port": 1,
|
||||||
|
"wired-rx_bytes": 1234000000,
|
||||||
|
"wired-tx_bytes": 5678000000,
|
||||||
|
"uptime": 1562600160,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_discovery():
|
def mock_discovery():
|
||||||
|
@ -1,242 +1,25 @@
|
|||||||
"""Test UniFi Network."""
|
"""Test UniFi Network."""
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from copy import deepcopy
|
|
||||||
from datetime import timedelta
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
from types import MappingProxyType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import aiounifi
|
import aiounifi
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN
|
from homeassistant.components.unifi.const import DOMAIN as UNIFI_DOMAIN
|
||||||
from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN
|
|
||||||
from homeassistant.components.image import DOMAIN as IMAGE_DOMAIN
|
|
||||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
|
||||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
|
||||||
from homeassistant.components.unifi.const import (
|
|
||||||
CONF_SITE_ID,
|
|
||||||
DEFAULT_ALLOW_BANDWIDTH_SENSORS,
|
|
||||||
DEFAULT_ALLOW_UPTIME_SENSORS,
|
|
||||||
DEFAULT_DETECTION_TIME,
|
|
||||||
DEFAULT_TRACK_CLIENTS,
|
|
||||||
DEFAULT_TRACK_DEVICES,
|
|
||||||
DEFAULT_TRACK_WIRED_CLIENTS,
|
|
||||||
DOMAIN as UNIFI_DOMAIN,
|
|
||||||
UNIFI_WIRELESS_CLIENTS,
|
|
||||||
)
|
|
||||||
from homeassistant.components.unifi.errors import AuthenticationRequired, CannotConnect
|
from homeassistant.components.unifi.errors import AuthenticationRequired, CannotConnect
|
||||||
from homeassistant.components.unifi.hub import get_unifi_api
|
from homeassistant.components.unifi.hub import get_unifi_api
|
||||||
from homeassistant.components.update import DOMAIN as UPDATE_DOMAIN
|
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||||
from homeassistant.const import (
|
from homeassistant.const import CONF_HOST, Platform
|
||||||
CONF_HOST,
|
|
||||||
CONF_PASSWORD,
|
|
||||||
CONF_PORT,
|
|
||||||
CONF_USERNAME,
|
|
||||||
CONF_VERIFY_SSL,
|
|
||||||
CONTENT_TYPE_JSON,
|
|
||||||
)
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.setup import async_setup_component
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
DEFAULT_CONFIG_ENTRY_ID = "1"
|
|
||||||
DEFAULT_HOST = "1.2.3.4"
|
|
||||||
DEFAULT_SITE = "site_id"
|
|
||||||
|
|
||||||
CONTROLLER_HOST = {
|
|
||||||
"hostname": "controller_host",
|
|
||||||
"ip": DEFAULT_HOST,
|
|
||||||
"is_wired": True,
|
|
||||||
"last_seen": 1562600145,
|
|
||||||
"mac": "10:00:00:00:00:01",
|
|
||||||
"name": "Controller host",
|
|
||||||
"oui": "Producer",
|
|
||||||
"sw_mac": "00:00:00:00:01:01",
|
|
||||||
"sw_port": 1,
|
|
||||||
"wired-rx_bytes": 1234000000,
|
|
||||||
"wired-tx_bytes": 5678000000,
|
|
||||||
"uptime": 1562600160,
|
|
||||||
}
|
|
||||||
|
|
||||||
ENTRY_CONFIG = {
|
|
||||||
CONF_HOST: DEFAULT_HOST,
|
|
||||||
CONF_USERNAME: "username",
|
|
||||||
CONF_PASSWORD: "password",
|
|
||||||
CONF_PORT: 1234,
|
|
||||||
CONF_SITE_ID: DEFAULT_SITE,
|
|
||||||
CONF_VERIFY_SSL: False,
|
|
||||||
}
|
|
||||||
ENTRY_OPTIONS = {}
|
|
||||||
|
|
||||||
CONFIGURATION = []
|
|
||||||
|
|
||||||
SITE = [{"desc": "Site name", "name": "site_id", "role": "admin", "_id": "1"}]
|
|
||||||
|
|
||||||
SYSTEM_INFORMATION = [
|
|
||||||
{
|
|
||||||
"anonymous_controller_id": "24f81231-a456-4c32-abcd-f5612345385f",
|
|
||||||
"build": "atag_7.4.162_21057",
|
|
||||||
"console_display_version": "3.1.15",
|
|
||||||
"hostname": "UDMP",
|
|
||||||
"name": "UDMP",
|
|
||||||
"previous_version": "7.4.156",
|
|
||||||
"timezone": "Europe/Stockholm",
|
|
||||||
"ubnt_device_type": "UDMPRO",
|
|
||||||
"udm_version": "3.0.20.9281",
|
|
||||||
"update_available": False,
|
|
||||||
"update_downloaded": False,
|
|
||||||
"uptime": 1196290,
|
|
||||||
"version": "7.4.162",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def mock_default_unifi_requests(
|
|
||||||
aioclient_mock,
|
|
||||||
host,
|
|
||||||
site_id,
|
|
||||||
sites=None,
|
|
||||||
clients_response=None,
|
|
||||||
clients_all_response=None,
|
|
||||||
devices_response=None,
|
|
||||||
dpiapp_response=None,
|
|
||||||
dpigroup_response=None,
|
|
||||||
port_forward_response=None,
|
|
||||||
system_information_response=None,
|
|
||||||
wlans_response=None,
|
|
||||||
):
|
|
||||||
"""Mock default UniFi requests responses."""
|
|
||||||
aioclient_mock.get(f"https://{host}:1234", status=302) # Check UniFi OS
|
|
||||||
|
|
||||||
aioclient_mock.post(
|
|
||||||
f"https://{host}:1234/api/login",
|
|
||||||
json={"data": "login successful", "meta": {"rc": "ok"}},
|
|
||||||
headers={"content-type": CONTENT_TYPE_JSON},
|
|
||||||
)
|
|
||||||
|
|
||||||
aioclient_mock.get(
|
|
||||||
f"https://{host}:1234/api/self/sites",
|
|
||||||
json={"data": sites or [], "meta": {"rc": "ok"}},
|
|
||||||
headers={"content-type": CONTENT_TYPE_JSON},
|
|
||||||
)
|
|
||||||
|
|
||||||
aioclient_mock.get(
|
|
||||||
f"https://{host}:1234/api/s/{site_id}/stat/sta",
|
|
||||||
json={"data": clients_response or [], "meta": {"rc": "ok"}},
|
|
||||||
headers={"content-type": CONTENT_TYPE_JSON},
|
|
||||||
)
|
|
||||||
aioclient_mock.get(
|
|
||||||
f"https://{host}:1234/api/s/{site_id}/rest/user",
|
|
||||||
json={"data": clients_all_response or [], "meta": {"rc": "ok"}},
|
|
||||||
headers={"content-type": CONTENT_TYPE_JSON},
|
|
||||||
)
|
|
||||||
aioclient_mock.get(
|
|
||||||
f"https://{host}:1234/api/s/{site_id}/stat/device",
|
|
||||||
json={"data": devices_response or [], "meta": {"rc": "ok"}},
|
|
||||||
headers={"content-type": CONTENT_TYPE_JSON},
|
|
||||||
)
|
|
||||||
aioclient_mock.get(
|
|
||||||
f"https://{host}:1234/api/s/{site_id}/rest/dpiapp",
|
|
||||||
json={"data": dpiapp_response or [], "meta": {"rc": "ok"}},
|
|
||||||
headers={"content-type": CONTENT_TYPE_JSON},
|
|
||||||
)
|
|
||||||
aioclient_mock.get(
|
|
||||||
f"https://{host}:1234/api/s/{site_id}/rest/dpigroup",
|
|
||||||
json={"data": dpigroup_response or [], "meta": {"rc": "ok"}},
|
|
||||||
headers={"content-type": CONTENT_TYPE_JSON},
|
|
||||||
)
|
|
||||||
aioclient_mock.get(
|
|
||||||
f"https://{host}:1234/api/s/{site_id}/rest/portforward",
|
|
||||||
json={"data": port_forward_response or [], "meta": {"rc": "ok"}},
|
|
||||||
headers={"content-type": CONTENT_TYPE_JSON},
|
|
||||||
)
|
|
||||||
aioclient_mock.get(
|
|
||||||
f"https://{host}:1234/api/s/{site_id}/stat/sysinfo",
|
|
||||||
json={"data": system_information_response or [], "meta": {"rc": "ok"}},
|
|
||||||
headers={"content-type": CONTENT_TYPE_JSON},
|
|
||||||
)
|
|
||||||
aioclient_mock.get(
|
|
||||||
f"https://{host}:1234/api/s/{site_id}/rest/wlanconf",
|
|
||||||
json={"data": wlans_response or [], "meta": {"rc": "ok"}},
|
|
||||||
headers={"content-type": CONTENT_TYPE_JSON},
|
|
||||||
)
|
|
||||||
aioclient_mock.get(
|
|
||||||
f"https://{host}:1234/v2/api/site/{site_id}/trafficroutes",
|
|
||||||
json=[{}],
|
|
||||||
headers={"content-type": CONTENT_TYPE_JSON},
|
|
||||||
)
|
|
||||||
aioclient_mock.get(
|
|
||||||
f"https://{host}:1234/v2/api/site/{site_id}/trafficrules",
|
|
||||||
json=[{}],
|
|
||||||
headers={"content-type": CONTENT_TYPE_JSON},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def setup_unifi_integration(
|
|
||||||
hass,
|
|
||||||
aioclient_mock=None,
|
|
||||||
*,
|
|
||||||
config=ENTRY_CONFIG,
|
|
||||||
options=ENTRY_OPTIONS,
|
|
||||||
sites=SITE,
|
|
||||||
clients_response=None,
|
|
||||||
clients_all_response=None,
|
|
||||||
devices_response=None,
|
|
||||||
dpiapp_response=None,
|
|
||||||
dpigroup_response=None,
|
|
||||||
port_forward_response=None,
|
|
||||||
system_information_response=None,
|
|
||||||
wlans_response=None,
|
|
||||||
known_wireless_clients=None,
|
|
||||||
unique_id="1",
|
|
||||||
config_entry_id=DEFAULT_CONFIG_ENTRY_ID,
|
|
||||||
):
|
|
||||||
"""Create the UniFi Network instance."""
|
|
||||||
assert await async_setup_component(hass, UNIFI_DOMAIN, {})
|
|
||||||
|
|
||||||
config_entry = MockConfigEntry(
|
|
||||||
domain=UNIFI_DOMAIN,
|
|
||||||
data=deepcopy(config),
|
|
||||||
options=deepcopy(options),
|
|
||||||
unique_id=unique_id,
|
|
||||||
entry_id=config_entry_id,
|
|
||||||
version=1,
|
|
||||||
)
|
|
||||||
config_entry.add_to_hass(hass)
|
|
||||||
|
|
||||||
if known_wireless_clients:
|
|
||||||
hass.data[UNIFI_WIRELESS_CLIENTS].wireless_clients.update(
|
|
||||||
known_wireless_clients
|
|
||||||
)
|
|
||||||
|
|
||||||
if aioclient_mock:
|
|
||||||
mock_default_unifi_requests(
|
|
||||||
aioclient_mock,
|
|
||||||
host=config_entry.data[CONF_HOST],
|
|
||||||
site_id=config_entry.data[CONF_SITE_ID],
|
|
||||||
sites=sites,
|
|
||||||
clients_response=clients_response,
|
|
||||||
clients_all_response=clients_all_response,
|
|
||||||
devices_response=devices_response,
|
|
||||||
dpiapp_response=dpiapp_response,
|
|
||||||
dpigroup_response=dpigroup_response,
|
|
||||||
port_forward_response=port_forward_response,
|
|
||||||
system_information_response=system_information_response,
|
|
||||||
wlans_response=wlans_response,
|
|
||||||
)
|
|
||||||
|
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
return config_entry
|
|
||||||
|
|
||||||
|
|
||||||
async def test_hub_setup(
|
async def test_hub_setup(
|
||||||
device_registry: dr.DeviceRegistry,
|
device_registry: dr.DeviceRegistry,
|
||||||
@ -248,38 +31,20 @@ async def test_hub_setup(
|
|||||||
return_value=True,
|
return_value=True,
|
||||||
) as forward_entry_setup:
|
) as forward_entry_setup:
|
||||||
config_entry = await config_entry_factory()
|
config_entry = await config_entry_factory()
|
||||||
hub = config_entry.runtime_data
|
|
||||||
|
|
||||||
entry = hub.config.entry
|
|
||||||
assert len(forward_entry_setup.mock_calls) == 1
|
assert len(forward_entry_setup.mock_calls) == 1
|
||||||
assert forward_entry_setup.mock_calls[0][1] == (
|
assert forward_entry_setup.mock_calls[0][1] == (
|
||||||
entry,
|
config_entry,
|
||||||
[
|
[
|
||||||
BUTTON_DOMAIN,
|
Platform.BUTTON,
|
||||||
TRACKER_DOMAIN,
|
Platform.DEVICE_TRACKER,
|
||||||
IMAGE_DOMAIN,
|
Platform.IMAGE,
|
||||||
SENSOR_DOMAIN,
|
Platform.SENSOR,
|
||||||
SWITCH_DOMAIN,
|
Platform.SWITCH,
|
||||||
UPDATE_DOMAIN,
|
Platform.UPDATE,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
assert hub.config.host == ENTRY_CONFIG[CONF_HOST]
|
|
||||||
assert hub.is_admin == (SITE[0]["role"] == "admin")
|
|
||||||
|
|
||||||
assert hub.config.option_allow_bandwidth_sensors == DEFAULT_ALLOW_BANDWIDTH_SENSORS
|
|
||||||
assert hub.config.option_allow_uptime_sensors == DEFAULT_ALLOW_UPTIME_SENSORS
|
|
||||||
assert isinstance(hub.config.option_block_clients, list)
|
|
||||||
assert hub.config.option_track_clients == DEFAULT_TRACK_CLIENTS
|
|
||||||
assert hub.config.option_track_devices == DEFAULT_TRACK_DEVICES
|
|
||||||
assert hub.config.option_track_wired_clients == DEFAULT_TRACK_WIRED_CLIENTS
|
|
||||||
assert hub.config.option_detection_time == timedelta(seconds=DEFAULT_DETECTION_TIME)
|
|
||||||
assert isinstance(hub.config.option_ssid_filter, set)
|
|
||||||
|
|
||||||
assert hub.signal_reachable == "unifi-reachable-1"
|
|
||||||
assert hub.signal_options_update == "unifi-options-1"
|
|
||||||
assert hub.signal_heartbeat_missed == "unifi-heartbeat-missed"
|
|
||||||
|
|
||||||
device_entry = device_registry.async_get_or_create(
|
device_entry = device_registry.async_get_or_create(
|
||||||
config_entry_id=config_entry.entry_id,
|
config_entry_id=config_entry.entry_id,
|
||||||
identifiers={(UNIFI_DOMAIN, config_entry.unique_id)},
|
identifiers={(UNIFI_DOMAIN, config_entry.unique_id)},
|
||||||
@ -292,26 +57,24 @@ async def test_reset_after_successful_setup(
|
|||||||
hass: HomeAssistant, config_entry_setup: ConfigEntry
|
hass: HomeAssistant, config_entry_setup: ConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Calling reset when the entry has been setup."""
|
"""Calling reset when the entry has been setup."""
|
||||||
config_entry = config_entry_setup
|
assert config_entry_setup.state is ConfigEntryState.LOADED
|
||||||
assert config_entry.state is ConfigEntryState.LOADED
|
|
||||||
|
|
||||||
assert await hass.config_entries.async_unload(config_entry.entry_id)
|
assert await hass.config_entries.async_unload(config_entry_setup.entry_id)
|
||||||
assert config_entry.state is ConfigEntryState.NOT_LOADED
|
assert config_entry_setup.state is ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
async def test_reset_fails(
|
async def test_reset_fails(
|
||||||
hass: HomeAssistant, config_entry_setup: ConfigEntry
|
hass: HomeAssistant, config_entry_setup: ConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Calling reset when the entry has been setup can return false."""
|
"""Calling reset when the entry has been setup can return false."""
|
||||||
config_entry = config_entry_setup
|
assert config_entry_setup.state is ConfigEntryState.LOADED
|
||||||
assert config_entry.state is ConfigEntryState.LOADED
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.config_entries.ConfigEntries.async_forward_entry_unload",
|
"homeassistant.config_entries.ConfigEntries.async_forward_entry_unload",
|
||||||
return_value=False,
|
return_value=False,
|
||||||
):
|
):
|
||||||
assert not await hass.config_entries.async_unload(config_entry.entry_id)
|
assert not await hass.config_entries.async_unload(config_entry_setup.entry_id)
|
||||||
assert config_entry.state is ConfigEntryState.LOADED
|
assert config_entry_setup.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
|
||||||
async def test_connection_state_signalling(
|
async def test_connection_state_signalling(
|
||||||
@ -346,14 +109,14 @@ async def test_connection_state_signalling(
|
|||||||
|
|
||||||
|
|
||||||
async def test_reconnect_mechanism(
|
async def test_reconnect_mechanism(
|
||||||
hass: HomeAssistant,
|
aioclient_mock: AiohttpClientMocker, websocket_mock, config_entry_setup: ConfigEntry
|
||||||
aioclient_mock: AiohttpClientMocker,
|
|
||||||
config_entry_setup: ConfigEntry,
|
|
||||||
websocket_mock,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Verify reconnect prints only on first reconnection try."""
|
"""Verify reconnect prints only on first reconnection try."""
|
||||||
aioclient_mock.clear_requests()
|
aioclient_mock.clear_requests()
|
||||||
aioclient_mock.get(f"https://{DEFAULT_HOST}:1234/", status=HTTPStatus.BAD_GATEWAY)
|
aioclient_mock.get(
|
||||||
|
f"https://{config_entry_setup.data[CONF_HOST]}:1234/",
|
||||||
|
status=HTTPStatus.BAD_GATEWAY,
|
||||||
|
)
|
||||||
|
|
||||||
await websocket_mock.disconnect()
|
await websocket_mock.disconnect()
|
||||||
assert aioclient_mock.call_count == 0
|
assert aioclient_mock.call_count == 0
|
||||||
@ -374,13 +137,8 @@ async def test_reconnect_mechanism(
|
|||||||
aiounifi.AiounifiException,
|
aiounifi.AiounifiException,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_reconnect_mechanism_exceptions(
|
@pytest.mark.usefixtures("config_entry_setup")
|
||||||
hass: HomeAssistant,
|
async def test_reconnect_mechanism_exceptions(websocket_mock, exception) -> None:
|
||||||
aioclient_mock: AiohttpClientMocker,
|
|
||||||
config_entry_setup: ConfigEntry,
|
|
||||||
websocket_mock,
|
|
||||||
exception,
|
|
||||||
) -> None:
|
|
||||||
"""Verify async_reconnect calls expected methods."""
|
"""Verify async_reconnect calls expected methods."""
|
||||||
with (
|
with (
|
||||||
patch("aiounifi.Controller.login", side_effect=exception),
|
patch("aiounifi.Controller.login", side_effect=exception),
|
||||||
@ -409,11 +167,14 @@ async def test_reconnect_mechanism_exceptions(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_get_unifi_api_fails_to_connect(
|
async def test_get_unifi_api_fails_to_connect(
|
||||||
hass: HomeAssistant, side_effect, raised_exception
|
hass: HomeAssistant,
|
||||||
|
side_effect,
|
||||||
|
raised_exception,
|
||||||
|
config_entry_data: MappingProxyType[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Check that get_unifi_api can handle UniFi Network being unavailable."""
|
"""Check that get_unifi_api can handle UniFi Network being unavailable."""
|
||||||
with (
|
with (
|
||||||
patch("aiounifi.Controller.login", side_effect=side_effect),
|
patch("aiounifi.Controller.login", side_effect=side_effect),
|
||||||
pytest.raises(raised_exception),
|
pytest.raises(raised_exception),
|
||||||
):
|
):
|
||||||
await get_unifi_api(hass, ENTRY_CONFIG)
|
await get_unifi_api(hass, config_entry_data)
|
||||||
|
@ -21,7 +21,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from .test_hub import DEFAULT_CONFIG_ENTRY_ID
|
from .conftest import DEFAULT_CONFIG_ENTRY_ID
|
||||||
|
|
||||||
from tests.common import flush_store
|
from tests.common import flush_store
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
@ -35,7 +35,7 @@ from homeassistant.helpers import entity_registry as er
|
|||||||
from homeassistant.helpers.entity_registry import RegistryEntryDisabler
|
from homeassistant.helpers.entity_registry import RegistryEntryDisabler
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .test_hub import CONTROLLER_HOST
|
from .conftest import CONTROLLER_HOST
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import async_fire_time_changed
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
Loading…
x
Reference in New Issue
Block a user