Add additional data source to dhcp (#48430)

This commit is contained in:
J. Nick Koston 2021-03-28 09:47:28 -10:00 committed by GitHub
parent 23c7c4c977
commit 2ff94c8ed9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 235 additions and 60 deletions

View file

@ -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",
}