mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Add additional data source to dhcp (#48430)
This commit is contained in:
parent
23c7c4c977
commit
2ff94c8ed9
@ -1,12 +1,19 @@
|
||||
"""The dhcp integration."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from datetime import timedelta
|
||||
import fnmatch
|
||||
from ipaddress import ip_address as make_ip_address
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
|
||||
from aiodiscover import DiscoverHosts
|
||||
from aiodiscover.discovery import (
|
||||
HOSTNAME as DISCOVERY_HOSTNAME,
|
||||
IP_ADDRESS as DISCOVERY_IP_ADDRESS,
|
||||
MAC_ADDRESS as DISCOVERY_MAC_ADDRESS,
|
||||
)
|
||||
from scapy.arch.common import compile_filter
|
||||
from scapy.config import conf
|
||||
from scapy.error import Scapy_Exception
|
||||
@ -29,7 +36,10 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant, State, callback
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.helpers.event import async_track_state_added_domain
|
||||
from homeassistant.helpers.event import (
|
||||
async_track_state_added_domain,
|
||||
async_track_time_interval,
|
||||
)
|
||||
from homeassistant.loader import async_get_dhcp
|
||||
from homeassistant.util.network import is_link_local
|
||||
|
||||
@ -42,6 +52,7 @@ HOSTNAME = "hostname"
|
||||
MAC_ADDRESS = "macaddress"
|
||||
IP_ADDRESS = "ip"
|
||||
DHCP_REQUEST = 3
|
||||
SCAN_INTERVAL = timedelta(minutes=60)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -54,7 +65,7 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
|
||||
integration_matchers = await async_get_dhcp(hass)
|
||||
watchers = []
|
||||
|
||||
for cls in (DHCPWatcher, DeviceTrackerWatcher):
|
||||
for cls in (DHCPWatcher, DeviceTrackerWatcher, NetworkWatcher):
|
||||
watcher = cls(hass, address_data, integration_matchers)
|
||||
await watcher.async_start()
|
||||
watchers.append(watcher)
|
||||
@ -88,7 +99,11 @@ class WatcherBase:
|
||||
|
||||
data = self._address_data.get(ip_address)
|
||||
|
||||
if data and data[MAC_ADDRESS] == mac_address and data[HOSTNAME] == hostname:
|
||||
if (
|
||||
data
|
||||
and data[MAC_ADDRESS] == mac_address
|
||||
and data[HOSTNAME].startswith(hostname)
|
||||
):
|
||||
# If the address data is the same no need
|
||||
# to process it
|
||||
return
|
||||
@ -139,6 +154,54 @@ class WatcherBase:
|
||||
"""Pass a task to async_add_task based on which context we are in."""
|
||||
|
||||
|
||||
class NetworkWatcher(WatcherBase):
|
||||
"""Class to query ptr records routers."""
|
||||
|
||||
def __init__(self, hass, address_data, integration_matchers):
|
||||
"""Initialize class."""
|
||||
super().__init__(hass, address_data, integration_matchers)
|
||||
self._unsub = None
|
||||
self._discover_hosts = None
|
||||
self._discover_task = None
|
||||
|
||||
async def async_stop(self):
|
||||
"""Stop scanning for new devices on the network."""
|
||||
if self._unsub:
|
||||
self._unsub()
|
||||
self._unsub = None
|
||||
if self._discover_task:
|
||||
self._discover_task.cancel()
|
||||
self._discover_task = None
|
||||
|
||||
async def async_start(self):
|
||||
"""Start scanning for new devices on the network."""
|
||||
self._discover_hosts = DiscoverHosts()
|
||||
self._unsub = async_track_time_interval(
|
||||
self.hass, self.async_start_discover, SCAN_INTERVAL
|
||||
)
|
||||
self.async_start_discover()
|
||||
|
||||
@callback
|
||||
def async_start_discover(self, *_):
|
||||
"""Start a new discovery task if one is not running."""
|
||||
if self._discover_task and not self._discover_task.done():
|
||||
return
|
||||
self._discover_task = self.create_task(self.async_discover())
|
||||
|
||||
async def async_discover(self):
|
||||
"""Process discovery."""
|
||||
for host in await self._discover_hosts.async_discover():
|
||||
self.process_client(
|
||||
host[DISCOVERY_IP_ADDRESS],
|
||||
host[DISCOVERY_HOSTNAME],
|
||||
_format_mac(host[DISCOVERY_MAC_ADDRESS]),
|
||||
)
|
||||
|
||||
def create_task(self, task):
|
||||
"""Pass a task to async_create_task since we are in async context."""
|
||||
return self.hass.async_create_task(task)
|
||||
|
||||
|
||||
class DeviceTrackerWatcher(WatcherBase):
|
||||
"""Class to watch dhcp data from routers."""
|
||||
|
||||
@ -188,7 +251,7 @@ class DeviceTrackerWatcher(WatcherBase):
|
||||
|
||||
def create_task(self, task):
|
||||
"""Pass a task to async_create_task since we are in async context."""
|
||||
self.hass.async_create_task(task)
|
||||
return self.hass.async_create_task(task)
|
||||
|
||||
|
||||
class DHCPWatcher(WatcherBase):
|
||||
@ -266,7 +329,7 @@ class DHCPWatcher(WatcherBase):
|
||||
|
||||
def create_task(self, task):
|
||||
"""Pass a task to hass.add_job since we are in a thread."""
|
||||
self.hass.add_job(task)
|
||||
return self.hass.add_job(task)
|
||||
|
||||
|
||||
def _decode_dhcp_option(dhcp_options, key):
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "DHCP Discovery",
|
||||
"documentation": "https://www.home-assistant.io/integrations/dhcp",
|
||||
"requirements": [
|
||||
"scapy==2.4.4"
|
||||
"scapy==2.4.4", "aiodiscover==1.1.0"
|
||||
],
|
||||
"codeowners": [
|
||||
"@bdraco"
|
||||
|
@ -1,5 +1,6 @@
|
||||
PyJWT==1.7.1
|
||||
PyNaCl==1.3.0
|
||||
aiodiscover==1.1.0
|
||||
aiohttp==3.7.4.post0
|
||||
aiohttp_cors==0.7.0
|
||||
astral==1.10.1
|
||||
|
@ -146,6 +146,9 @@ aioazuredevops==1.3.5
|
||||
# homeassistant.components.aws
|
||||
aiobotocore==0.11.1
|
||||
|
||||
# homeassistant.components.dhcp
|
||||
aiodiscover==1.1.0
|
||||
|
||||
# homeassistant.components.dnsip
|
||||
# homeassistant.components.minecraft_server
|
||||
aiodns==2.0.0
|
||||
|
@ -83,6 +83,9 @@ aioazuredevops==1.3.5
|
||||
# homeassistant.components.aws
|
||||
aiobotocore==0.11.1
|
||||
|
||||
# homeassistant.components.dhcp
|
||||
aiodiscover==1.1.0
|
||||
|
||||
# homeassistant.components.dnsip
|
||||
# homeassistant.components.minecraft_server
|
||||
aiodns==2.0.0
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""Test the DHCP discovery integration."""
|
||||
import datetime
|
||||
import threading
|
||||
from unittest.mock import patch
|
||||
|
||||
@ -21,8 +22,9 @@ from homeassistant.const import (
|
||||
STATE_NOT_HOME,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from tests.common import mock_coro
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
# connect b8:b7:f1:6d:b5:33 192.168.210.56
|
||||
RAW_DHCP_REQUEST = (
|
||||
@ -59,9 +61,7 @@ async def test_dhcp_match_hostname_and_macaddress(hass):
|
||||
|
||||
packet = Ether(RAW_DHCP_REQUEST)
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
dhcp_watcher.handle_dhcp_packet(packet)
|
||||
# Ensure no change is ignored
|
||||
dhcp_watcher.handle_dhcp_packet(packet)
|
||||
@ -84,9 +84,7 @@ async def test_dhcp_match_hostname(hass):
|
||||
|
||||
packet = Ether(RAW_DHCP_REQUEST)
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
dhcp_watcher.handle_dhcp_packet(packet)
|
||||
|
||||
assert len(mock_init.mock_calls) == 1
|
||||
@ -107,9 +105,7 @@ async def test_dhcp_match_macaddress(hass):
|
||||
|
||||
packet = Ether(RAW_DHCP_REQUEST)
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
dhcp_watcher.handle_dhcp_packet(packet)
|
||||
|
||||
assert len(mock_init.mock_calls) == 1
|
||||
@ -130,9 +126,7 @@ async def test_dhcp_nomatch(hass):
|
||||
|
||||
packet = Ether(RAW_DHCP_REQUEST)
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
dhcp_watcher.handle_dhcp_packet(packet)
|
||||
|
||||
assert len(mock_init.mock_calls) == 0
|
||||
@ -146,9 +140,7 @@ async def test_dhcp_nomatch_hostname(hass):
|
||||
|
||||
packet = Ether(RAW_DHCP_REQUEST)
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
dhcp_watcher.handle_dhcp_packet(packet)
|
||||
|
||||
assert len(mock_init.mock_calls) == 0
|
||||
@ -162,9 +154,7 @@ async def test_dhcp_nomatch_non_dhcp_packet(hass):
|
||||
|
||||
packet = Ether(b"")
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
dhcp_watcher.handle_dhcp_packet(packet)
|
||||
|
||||
assert len(mock_init.mock_calls) == 0
|
||||
@ -187,9 +177,7 @@ async def test_dhcp_nomatch_non_dhcp_request_packet(hass):
|
||||
("hostname", b"connect"),
|
||||
]
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
dhcp_watcher.handle_dhcp_packet(packet)
|
||||
|
||||
assert len(mock_init.mock_calls) == 0
|
||||
@ -212,9 +200,7 @@ async def test_dhcp_invalid_hostname(hass):
|
||||
("hostname", "connect"),
|
||||
]
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
dhcp_watcher.handle_dhcp_packet(packet)
|
||||
|
||||
assert len(mock_init.mock_calls) == 0
|
||||
@ -237,9 +223,7 @@ async def test_dhcp_missing_hostname(hass):
|
||||
("hostname", None),
|
||||
]
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
dhcp_watcher.handle_dhcp_packet(packet)
|
||||
|
||||
assert len(mock_init.mock_calls) == 0
|
||||
@ -262,9 +246,7 @@ async def test_dhcp_invalid_option(hass):
|
||||
("hostname"),
|
||||
]
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
dhcp_watcher.handle_dhcp_packet(packet)
|
||||
|
||||
assert len(mock_init.mock_calls) == 0
|
||||
@ -282,8 +264,8 @@ async def test_setup_and_stop(hass):
|
||||
|
||||
with patch("homeassistant.components.dhcp.AsyncSniffer.start") as start_call, patch(
|
||||
"homeassistant.components.dhcp._verify_l2socket_setup",
|
||||
), patch(
|
||||
"homeassistant.components.dhcp.compile_filter",
|
||||
), patch("homeassistant.components.dhcp.compile_filter",), patch(
|
||||
"homeassistant.components.dhcp.DiscoverHosts.async_discover"
|
||||
):
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
@ -309,7 +291,7 @@ async def test_setup_fails_as_root(hass, caplog):
|
||||
with patch("os.geteuid", return_value=0), patch(
|
||||
"homeassistant.components.dhcp._verify_l2socket_setup",
|
||||
side_effect=Scapy_Exception,
|
||||
):
|
||||
), patch("homeassistant.components.dhcp.DiscoverHosts.async_discover"):
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -332,7 +314,7 @@ async def test_setup_fails_non_root(hass, caplog):
|
||||
with patch("os.geteuid", return_value=10), patch(
|
||||
"homeassistant.components.dhcp._verify_l2socket_setup",
|
||||
side_effect=Scapy_Exception,
|
||||
):
|
||||
), patch("homeassistant.components.dhcp.DiscoverHosts.async_discover"):
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||
@ -356,7 +338,9 @@ async def test_setup_fails_with_broken_libpcap(hass, caplog):
|
||||
side_effect=ImportError,
|
||||
) as compile_filter, patch(
|
||||
"homeassistant.components.dhcp.AsyncSniffer",
|
||||
) as async_sniffer:
|
||||
) as async_sniffer, patch(
|
||||
"homeassistant.components.dhcp.DiscoverHosts.async_discover"
|
||||
):
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||
@ -383,9 +367,7 @@ async def test_device_tracker_hostname_and_macaddress_exists_before_start(hass):
|
||||
},
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
device_tracker_watcher = dhcp.DeviceTrackerWatcher(
|
||||
hass,
|
||||
{},
|
||||
@ -409,9 +391,7 @@ async def test_device_tracker_hostname_and_macaddress_exists_before_start(hass):
|
||||
async def test_device_tracker_hostname_and_macaddress_after_start(hass):
|
||||
"""Test matching based on hostname and macaddress after start."""
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
device_tracker_watcher = dhcp.DeviceTrackerWatcher(
|
||||
hass,
|
||||
{},
|
||||
@ -446,9 +426,7 @@ async def test_device_tracker_hostname_and_macaddress_after_start(hass):
|
||||
async def test_device_tracker_hostname_and_macaddress_after_start_not_home(hass):
|
||||
"""Test matching based on hostname and macaddress after start but not home."""
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
device_tracker_watcher = dhcp.DeviceTrackerWatcher(
|
||||
hass,
|
||||
{},
|
||||
@ -476,9 +454,7 @@ async def test_device_tracker_hostname_and_macaddress_after_start_not_home(hass)
|
||||
async def test_device_tracker_hostname_and_macaddress_after_start_not_router(hass):
|
||||
"""Test matching based on hostname and macaddress after start but not router."""
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
device_tracker_watcher = dhcp.DeviceTrackerWatcher(
|
||||
hass,
|
||||
{},
|
||||
@ -508,9 +484,7 @@ async def test_device_tracker_hostname_and_macaddress_after_start_hostname_missi
|
||||
):
|
||||
"""Test matching based on hostname and macaddress after start but missing hostname."""
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
device_tracker_watcher = dhcp.DeviceTrackerWatcher(
|
||||
hass,
|
||||
{},
|
||||
@ -547,9 +521,7 @@ async def test_device_tracker_ignore_self_assigned_ips_before_start(hass):
|
||||
},
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init:
|
||||
device_tracker_watcher = dhcp.DeviceTrackerWatcher(
|
||||
hass,
|
||||
{},
|
||||
@ -561,3 +533,136 @@ async def test_device_tracker_ignore_self_assigned_ips_before_start(hass):
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_init.mock_calls) == 0
|
||||
|
||||
|
||||
async def test_aiodiscover_finds_new_hosts(hass):
|
||||
"""Test aiodiscover finds new host."""
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init, patch(
|
||||
"homeassistant.components.dhcp.DiscoverHosts.async_discover",
|
||||
return_value=[
|
||||
{
|
||||
dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56",
|
||||
dhcp.DISCOVERY_HOSTNAME: "connect",
|
||||
dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533",
|
||||
}
|
||||
],
|
||||
):
|
||||
device_tracker_watcher = dhcp.NetworkWatcher(
|
||||
hass,
|
||||
{},
|
||||
[{"domain": "mock-domain", "hostname": "connect", "macaddress": "B8B7F1*"}],
|
||||
)
|
||||
await device_tracker_watcher.async_start()
|
||||
await hass.async_block_till_done()
|
||||
await device_tracker_watcher.async_stop()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_init.mock_calls) == 1
|
||||
assert mock_init.mock_calls[0][1][0] == "mock-domain"
|
||||
assert mock_init.mock_calls[0][2]["context"] == {"source": "dhcp"}
|
||||
assert mock_init.mock_calls[0][2]["data"] == {
|
||||
dhcp.IP_ADDRESS: "192.168.210.56",
|
||||
dhcp.HOSTNAME: "connect",
|
||||
dhcp.MAC_ADDRESS: "b8b7f16db533",
|
||||
}
|
||||
|
||||
|
||||
async def test_aiodiscover_does_not_call_again_on_shorter_hostname(hass):
|
||||
"""Verify longer hostnames generate a new flow but shorter ones do not.
|
||||
|
||||
Some routers will truncate hostnames so we want to accept
|
||||
additional discovery where the hostname is longer and then
|
||||
reject shorter ones.
|
||||
"""
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init, patch(
|
||||
"homeassistant.components.dhcp.DiscoverHosts.async_discover",
|
||||
return_value=[
|
||||
{
|
||||
dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56",
|
||||
dhcp.DISCOVERY_HOSTNAME: "irobot-abc",
|
||||
dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533",
|
||||
},
|
||||
{
|
||||
dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56",
|
||||
dhcp.DISCOVERY_HOSTNAME: "irobot-abcdef",
|
||||
dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533",
|
||||
},
|
||||
{
|
||||
dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56",
|
||||
dhcp.DISCOVERY_HOSTNAME: "irobot-abc",
|
||||
dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533",
|
||||
},
|
||||
],
|
||||
):
|
||||
device_tracker_watcher = dhcp.NetworkWatcher(
|
||||
hass,
|
||||
{},
|
||||
[
|
||||
{
|
||||
"domain": "mock-domain",
|
||||
"hostname": "irobot-*",
|
||||
"macaddress": "B8B7F1*",
|
||||
}
|
||||
],
|
||||
)
|
||||
await device_tracker_watcher.async_start()
|
||||
await hass.async_block_till_done()
|
||||
await device_tracker_watcher.async_stop()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_init.mock_calls) == 2
|
||||
assert mock_init.mock_calls[0][1][0] == "mock-domain"
|
||||
assert mock_init.mock_calls[0][2]["context"] == {"source": "dhcp"}
|
||||
assert mock_init.mock_calls[0][2]["data"] == {
|
||||
dhcp.IP_ADDRESS: "192.168.210.56",
|
||||
dhcp.HOSTNAME: "irobot-abc",
|
||||
dhcp.MAC_ADDRESS: "b8b7f16db533",
|
||||
}
|
||||
assert mock_init.mock_calls[1][1][0] == "mock-domain"
|
||||
assert mock_init.mock_calls[1][2]["context"] == {"source": "dhcp"}
|
||||
assert mock_init.mock_calls[1][2]["data"] == {
|
||||
dhcp.IP_ADDRESS: "192.168.210.56",
|
||||
dhcp.HOSTNAME: "irobot-abcdef",
|
||||
dhcp.MAC_ADDRESS: "b8b7f16db533",
|
||||
}
|
||||
|
||||
|
||||
async def test_aiodiscover_finds_new_hosts_after_interval(hass):
|
||||
"""Test aiodiscover finds new host after interval."""
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init, patch(
|
||||
"homeassistant.components.dhcp.DiscoverHosts.async_discover",
|
||||
return_value=[],
|
||||
):
|
||||
device_tracker_watcher = dhcp.NetworkWatcher(
|
||||
hass,
|
||||
{},
|
||||
[{"domain": "mock-domain", "hostname": "connect", "macaddress": "B8B7F1*"}],
|
||||
)
|
||||
await device_tracker_watcher.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_init.mock_calls) == 0
|
||||
|
||||
with patch.object(hass.config_entries.flow, "async_init") as mock_init, patch(
|
||||
"homeassistant.components.dhcp.DiscoverHosts.async_discover",
|
||||
return_value=[
|
||||
{
|
||||
dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56",
|
||||
dhcp.DISCOVERY_HOSTNAME: "connect",
|
||||
dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533",
|
||||
}
|
||||
],
|
||||
):
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(minutes=65))
|
||||
await hass.async_block_till_done()
|
||||
await device_tracker_watcher.async_stop()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_init.mock_calls) == 1
|
||||
assert mock_init.mock_calls[0][1][0] == "mock-domain"
|
||||
assert mock_init.mock_calls[0][2]["context"] == {"source": "dhcp"}
|
||||
assert mock_init.mock_calls[0][2]["data"] == {
|
||||
dhcp.IP_ADDRESS: "192.168.210.56",
|
||||
dhcp.HOSTNAME: "connect",
|
||||
dhcp.MAC_ADDRESS: "b8b7f16db533",
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user