mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Fix upnp device not being reinitialized after device changes location (#63133)
This commit is contained in:
parent
6d42af1b12
commit
9bf1397c54
@ -16,7 +16,7 @@ from homeassistant.components.ssdp import SsdpChange
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
import homeassistant.util.dt as dt_util
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
BYTES_RECEIVED,
|
BYTES_RECEIVED,
|
||||||
@ -31,6 +31,17 @@ from .const import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_create_upnp_device(
|
||||||
|
hass: HomeAssistant, ssdp_location: str
|
||||||
|
) -> UpnpDevice:
|
||||||
|
"""Create UPnP device."""
|
||||||
|
session = async_get_clientsession(hass)
|
||||||
|
requester = AiohttpSessionRequester(session, with_sleep=True, timeout=20)
|
||||||
|
|
||||||
|
factory = UpnpFactory(requester, disable_state_variable_validation=True)
|
||||||
|
return await factory.async_create_device(ssdp_location)
|
||||||
|
|
||||||
|
|
||||||
class Device:
|
class Device:
|
||||||
"""Home Assistant representation of a UPnP/IGD device."""
|
"""Home Assistant representation of a UPnP/IGD device."""
|
||||||
|
|
||||||
@ -40,25 +51,12 @@ class Device:
|
|||||||
self._igd_device = igd_device
|
self._igd_device = igd_device
|
||||||
self.coordinator: DataUpdateCoordinator = None
|
self.coordinator: DataUpdateCoordinator = None
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def async_create_upnp_device(
|
|
||||||
cls, hass: HomeAssistant, ssdp_location: str
|
|
||||||
) -> UpnpDevice:
|
|
||||||
"""Create UPnP device."""
|
|
||||||
# Build async_upnp_client requester.
|
|
||||||
session = async_get_clientsession(hass)
|
|
||||||
requester = AiohttpSessionRequester(session, True, 20)
|
|
||||||
|
|
||||||
# Create async_upnp_client device.
|
|
||||||
factory = UpnpFactory(requester, disable_state_variable_validation=True)
|
|
||||||
return await factory.async_create_device(ssdp_location)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def async_create_device(
|
async def async_create_device(
|
||||||
cls, hass: HomeAssistant, ssdp_location: str
|
cls, hass: HomeAssistant, ssdp_location: str
|
||||||
) -> Device:
|
) -> Device:
|
||||||
"""Create UPnP/IGD device."""
|
"""Create UPnP/IGD device."""
|
||||||
upnp_device = await Device.async_create_upnp_device(hass, ssdp_location)
|
upnp_device = await async_create_upnp_device(hass, ssdp_location)
|
||||||
|
|
||||||
# Create profile wrapper.
|
# Create profile wrapper.
|
||||||
igd_device = IgdDevice(upnp_device, None)
|
igd_device = IgdDevice(upnp_device, None)
|
||||||
@ -67,7 +65,7 @@ class Device:
|
|||||||
# Register SSDP callback for updates.
|
# Register SSDP callback for updates.
|
||||||
usn = f"{upnp_device.udn}::{upnp_device.device_type}"
|
usn = f"{upnp_device.udn}::{upnp_device.device_type}"
|
||||||
await ssdp.async_register_callback(
|
await ssdp.async_register_callback(
|
||||||
hass, device.async_ssdp_callback, {ssdp.ATTR_SSDP_USN: usn}
|
hass, device.async_ssdp_callback, {"usn": usn}
|
||||||
)
|
)
|
||||||
|
|
||||||
return device
|
return device
|
||||||
@ -76,7 +74,8 @@ class Device:
|
|||||||
self, headers: Mapping[str, Any], change: SsdpChange
|
self, headers: Mapping[str, Any], change: SsdpChange
|
||||||
) -> None:
|
) -> None:
|
||||||
"""SSDP callback, update if needed."""
|
"""SSDP callback, update if needed."""
|
||||||
if change != SsdpChange.UPDATE or ssdp.ATTR_SSDP_LOCATION not in headers:
|
_LOGGER.debug("SSDP Callback, change: %s, headers: %s", change, headers)
|
||||||
|
if ssdp.ATTR_SSDP_LOCATION not in headers:
|
||||||
return
|
return
|
||||||
|
|
||||||
location = headers[ssdp.ATTR_SSDP_LOCATION]
|
location = headers[ssdp.ATTR_SSDP_LOCATION]
|
||||||
@ -84,7 +83,7 @@ class Device:
|
|||||||
if location == device.device_url:
|
if location == device.device_url:
|
||||||
return
|
return
|
||||||
|
|
||||||
new_upnp_device = Device.async_create_upnp_device(self.hass, location)
|
new_upnp_device = await async_create_upnp_device(self.hass, location)
|
||||||
device.reinit(new_upnp_device)
|
device.reinit(new_upnp_device)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -155,7 +154,7 @@ class Device:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
TIMESTAMP: dt_util.utcnow(),
|
TIMESTAMP: utcnow(),
|
||||||
BYTES_RECEIVED: values[0],
|
BYTES_RECEIVED: values[0],
|
||||||
BYTES_SENT: values[1],
|
BYTES_SENT: values[1],
|
||||||
PACKETS_RECEIVED: values[2],
|
PACKETS_RECEIVED: values[2],
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
"""Configuration for SSDP tests."""
|
"""Configuration for SSDP tests."""
|
||||||
from typing import Any, Mapping
|
from typing import Optional, Sequence
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from async_upnp_client.client import UpnpDevice
|
||||||
|
from async_upnp_client.event_handler import UpnpEventHandler
|
||||||
|
from async_upnp_client.profiles.igd import StatusInfo
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import ssdp
|
from homeassistant.components import ssdp
|
||||||
@ -16,7 +19,6 @@ from homeassistant.components.upnp.const import (
|
|||||||
PACKETS_SENT,
|
PACKETS_SENT,
|
||||||
ROUTER_IP,
|
ROUTER_IP,
|
||||||
ROUTER_UPTIME,
|
ROUTER_UPTIME,
|
||||||
TIMESTAMP,
|
|
||||||
WAN_STATUS,
|
WAN_STATUS,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -29,17 +31,20 @@ TEST_ST = "urn:schemas-upnp-org:device:InternetGatewayDevice:1"
|
|||||||
TEST_USN = f"{TEST_UDN}::{TEST_ST}"
|
TEST_USN = f"{TEST_UDN}::{TEST_ST}"
|
||||||
TEST_LOCATION = "http://192.168.1.1/desc.xml"
|
TEST_LOCATION = "http://192.168.1.1/desc.xml"
|
||||||
TEST_HOSTNAME = urlparse(TEST_LOCATION).hostname
|
TEST_HOSTNAME = urlparse(TEST_LOCATION).hostname
|
||||||
TEST_FRIENDLY_NAME = "friendly name"
|
TEST_FRIENDLY_NAME = "mock-name"
|
||||||
TEST_DISCOVERY = ssdp.SsdpServiceInfo(
|
TEST_DISCOVERY = ssdp.SsdpServiceInfo(
|
||||||
ssdp_usn=TEST_USN,
|
ssdp_usn=TEST_USN,
|
||||||
ssdp_st=TEST_ST,
|
ssdp_st=TEST_ST,
|
||||||
ssdp_location=TEST_LOCATION,
|
ssdp_location=TEST_LOCATION,
|
||||||
upnp={
|
upnp={
|
||||||
ssdp.ATTR_UPNP_UDN: TEST_UDN,
|
|
||||||
"usn": TEST_USN,
|
|
||||||
"location": TEST_LOCATION,
|
|
||||||
"_udn": TEST_UDN,
|
"_udn": TEST_UDN,
|
||||||
"friendlyName": TEST_FRIENDLY_NAME,
|
"location": TEST_LOCATION,
|
||||||
|
"usn": TEST_USN,
|
||||||
|
ssdp.ATTR_UPNP_DEVICE_TYPE: TEST_ST,
|
||||||
|
ssdp.ATTR_UPNP_FRIENDLY_NAME: TEST_FRIENDLY_NAME,
|
||||||
|
ssdp.ATTR_UPNP_MANUFACTURER: "mock-manufacturer",
|
||||||
|
ssdp.ATTR_UPNP_MODEL_NAME: "mock-model-name",
|
||||||
|
ssdp.ATTR_UPNP_UDN: TEST_UDN,
|
||||||
},
|
},
|
||||||
ssdp_headers={
|
ssdp_headers={
|
||||||
"_host": TEST_HOSTNAME,
|
"_host": TEST_HOSTNAME,
|
||||||
@ -47,52 +52,37 @@ TEST_DISCOVERY = ssdp.SsdpServiceInfo(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MockDevice:
|
class MockUpnpDevice:
|
||||||
"""Mock device for Device."""
|
"""Mock async_upnp_client UpnpDevice."""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, udn: str) -> None:
|
def __init__(self, location: str) -> None:
|
||||||
"""Initialize mock device."""
|
"""Initialize."""
|
||||||
self.hass = hass
|
self.device_url = location
|
||||||
self._udn = udn
|
|
||||||
self.traffic_times_polled = 0
|
|
||||||
self.status_times_polled = 0
|
|
||||||
self._timestamp = dt.utcnow()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def async_create_device(cls, hass, ssdp_location) -> "MockDevice":
|
|
||||||
"""Return self."""
|
|
||||||
return cls(hass, TEST_UDN)
|
|
||||||
|
|
||||||
async def async_ssdp_callback(
|
|
||||||
self, headers: Mapping[str, Any], change: ssdp.SsdpChange
|
|
||||||
) -> None:
|
|
||||||
"""SSDP callback, update if needed."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
|
||||||
def udn(self) -> str:
|
|
||||||
"""Get the UDN."""
|
|
||||||
return self._udn
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def manufacturer(self) -> str:
|
def manufacturer(self) -> str:
|
||||||
"""Get manufacturer."""
|
"""Get manufacturer."""
|
||||||
return "mock-manufacturer"
|
return TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_MANUFACTURER]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Get name."""
|
"""Get name."""
|
||||||
return "mock-name"
|
return TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_FRIENDLY_NAME]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model_name(self) -> str:
|
def model_name(self) -> str:
|
||||||
"""Get the model name."""
|
"""Get the model name."""
|
||||||
return "mock-model-name"
|
return TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_MODEL_NAME]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_type(self) -> str:
|
def device_type(self) -> str:
|
||||||
"""Get the device type."""
|
"""Get the device type."""
|
||||||
return "urn:schemas-upnp-org:device:InternetGatewayDevice:1"
|
return TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_DEVICE_TYPE]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def udn(self) -> str:
|
||||||
|
"""Get the UDN."""
|
||||||
|
return TEST_DISCOVERY.upnp[ssdp.ATTR_UPNP_UDN]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def usn(self) -> str:
|
def usn(self) -> str:
|
||||||
@ -104,39 +94,118 @@ class MockDevice:
|
|||||||
"""Get the unique id."""
|
"""Get the unique id."""
|
||||||
return self.usn
|
return self.usn
|
||||||
|
|
||||||
@property
|
def reinit(self, new_upnp_device: UpnpDevice) -> None:
|
||||||
def hostname(self) -> str:
|
"""Reinitialize."""
|
||||||
"""Get the hostname."""
|
self.device_url = new_upnp_device.device_url
|
||||||
return "mock-hostname"
|
|
||||||
|
|
||||||
async def async_get_traffic_data(self) -> Mapping[str, Any]:
|
|
||||||
"""Get traffic data."""
|
class MockIgdDevice:
|
||||||
self.traffic_times_polled += 1
|
"""Mock async_upnp_client IgdDevice."""
|
||||||
return {
|
|
||||||
TIMESTAMP: self._timestamp,
|
def __init__(self, device: MockUpnpDevice, event_handler: UpnpEventHandler) -> None:
|
||||||
|
"""Initialize mock device."""
|
||||||
|
self.device = device
|
||||||
|
self.profile_device = device
|
||||||
|
|
||||||
|
self._timestamp = dt.utcnow()
|
||||||
|
self.traffic_times_polled = 0
|
||||||
|
self.status_times_polled = 0
|
||||||
|
|
||||||
|
self.traffic_data = {
|
||||||
BYTES_RECEIVED: 0,
|
BYTES_RECEIVED: 0,
|
||||||
BYTES_SENT: 0,
|
BYTES_SENT: 0,
|
||||||
PACKETS_RECEIVED: 0,
|
PACKETS_RECEIVED: 0,
|
||||||
PACKETS_SENT: 0,
|
PACKETS_SENT: 0,
|
||||||
}
|
}
|
||||||
|
self.status_data = {
|
||||||
async def async_get_status(self) -> Mapping[str, Any]:
|
|
||||||
"""Get connection status, uptime, and external IP."""
|
|
||||||
self.status_times_polled += 1
|
|
||||||
return {
|
|
||||||
WAN_STATUS: "Connected",
|
WAN_STATUS: "Connected",
|
||||||
ROUTER_UPTIME: 10,
|
ROUTER_UPTIME: 10,
|
||||||
ROUTER_IP: "8.9.10.11",
|
ROUTER_IP: "8.9.10.11",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Get the name of the device."""
|
||||||
|
return self.profile_device.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def manufacturer(self) -> str:
|
||||||
|
"""Get the manufacturer of this device."""
|
||||||
|
return self.profile_device.manufacturer
|
||||||
|
|
||||||
|
@property
|
||||||
|
def model_name(self) -> str:
|
||||||
|
"""Get the model name of this device."""
|
||||||
|
return self.profile_device.model_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def udn(self) -> str:
|
||||||
|
"""Get the UDN of the device."""
|
||||||
|
return self.profile_device.udn
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_type(self) -> str:
|
||||||
|
"""Get the device type of this device."""
|
||||||
|
return self.profile_device.device_type
|
||||||
|
|
||||||
|
async def async_get_total_bytes_received(self) -> Optional[int]:
|
||||||
|
"""Get total bytes received."""
|
||||||
|
self.traffic_times_polled += 1
|
||||||
|
return self.traffic_data[BYTES_RECEIVED]
|
||||||
|
|
||||||
|
async def async_get_total_bytes_sent(self) -> Optional[int]:
|
||||||
|
"""Get total bytes sent."""
|
||||||
|
return self.traffic_data[BYTES_SENT]
|
||||||
|
|
||||||
|
async def async_get_total_packets_received(self) -> Optional[int]:
|
||||||
|
"""Get total packets received."""
|
||||||
|
return self.traffic_data[PACKETS_RECEIVED]
|
||||||
|
|
||||||
|
async def async_get_total_packets_sent(self) -> Optional[int]:
|
||||||
|
"""Get total packets sent."""
|
||||||
|
return self.traffic_data[PACKETS_SENT]
|
||||||
|
|
||||||
|
async def async_get_external_ip_address(
|
||||||
|
self, services: Optional[Sequence[str]] = None
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Get the external IP address.
|
||||||
|
|
||||||
|
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
|
||||||
|
"""
|
||||||
|
return self.status_data[ROUTER_IP]
|
||||||
|
|
||||||
|
async def async_get_status_info(
|
||||||
|
self, services: Optional[Sequence[str]] = None
|
||||||
|
) -> Optional[StatusInfo]:
|
||||||
|
"""
|
||||||
|
Get status info.
|
||||||
|
|
||||||
|
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
|
||||||
|
"""
|
||||||
|
self.status_times_polled += 1
|
||||||
|
return StatusInfo(
|
||||||
|
self.status_data[WAN_STATUS], "", self.status_data[ROUTER_UPTIME]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_upnp_device():
|
def mock_upnp_device():
|
||||||
"""Mock homeassistant.components.upnp.Device."""
|
"""Mock homeassistant.components.upnp.Device."""
|
||||||
|
|
||||||
|
async def mock_async_create_upnp_device(
|
||||||
|
hass: HomeAssistant, location: str
|
||||||
|
) -> UpnpDevice:
|
||||||
|
"""Create UPnP device."""
|
||||||
|
return MockUpnpDevice(location)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.upnp.Device", new=MockDevice
|
"homeassistant.components.upnp.device.async_create_upnp_device",
|
||||||
) as mock_async_create_device:
|
side_effect=mock_async_create_upnp_device,
|
||||||
yield mock_async_create_device
|
) as mock_async_create_upnp_device, patch(
|
||||||
|
"homeassistant.components.upnp.device.IgdDevice", new=MockIgdDevice
|
||||||
|
) as mock_igd_device:
|
||||||
|
yield mock_async_create_upnp_device, mock_igd_device
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"""Tests for UPnP/IGD binary_sensor."""
|
"""Tests for UPnP/IGD binary_sensor."""
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from unittest.mock import AsyncMock
|
|
||||||
|
|
||||||
from homeassistant.components.upnp.const import (
|
from homeassistant.components.upnp.const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -12,7 +11,7 @@ from homeassistant.components.upnp.const import (
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .conftest import MockDevice
|
from .conftest import MockIgdDevice
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
@ -21,20 +20,19 @@ async def test_upnp_binary_sensors(
|
|||||||
hass: HomeAssistant, setup_integration: MockConfigEntry
|
hass: HomeAssistant, setup_integration: MockConfigEntry
|
||||||
):
|
):
|
||||||
"""Test normal sensors."""
|
"""Test normal sensors."""
|
||||||
mock_device: MockDevice = hass.data[DOMAIN][setup_integration.entry_id].device
|
|
||||||
|
|
||||||
# First poll.
|
# First poll.
|
||||||
wan_status_state = hass.states.get("binary_sensor.mock_name_wan_status")
|
wan_status_state = hass.states.get("binary_sensor.mock_name_wan_status")
|
||||||
assert wan_status_state.state == "on"
|
assert wan_status_state.state == "on"
|
||||||
|
|
||||||
# Second poll.
|
# Second poll.
|
||||||
mock_device.async_get_status = AsyncMock(
|
mock_device: MockIgdDevice = hass.data[DOMAIN][
|
||||||
return_value={
|
setup_integration.entry_id
|
||||||
WAN_STATUS: "Disconnected",
|
].device._igd_device
|
||||||
ROUTER_UPTIME: 100,
|
mock_device.status_data = {
|
||||||
ROUTER_IP: "",
|
WAN_STATUS: "Disconnected",
|
||||||
}
|
ROUTER_UPTIME: 100,
|
||||||
)
|
ROUTER_IP: "",
|
||||||
|
}
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=31))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=31))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ from .conftest import (
|
|||||||
TEST_ST,
|
TEST_ST,
|
||||||
TEST_UDN,
|
TEST_UDN,
|
||||||
TEST_USN,
|
TEST_USN,
|
||||||
MockDevice,
|
MockIgdDevice,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
@ -199,9 +199,11 @@ async def test_options_flow(hass: HomeAssistant):
|
|||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
assert await hass.config_entries.async_setup(config_entry.entry_id) is True
|
assert await hass.config_entries.async_setup(config_entry.entry_id) is True
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_device: MockDevice = hass.data[DOMAIN][config_entry.entry_id].device
|
|
||||||
|
|
||||||
# Reset.
|
# Reset.
|
||||||
|
mock_device: MockIgdDevice = hass.data[DOMAIN][
|
||||||
|
config_entry.entry_id
|
||||||
|
].device._igd_device
|
||||||
mock_device.traffic_times_polled = 0
|
mock_device.traffic_times_polled = 0
|
||||||
mock_device.status_times_polled = 0
|
mock_device.status_times_polled = 0
|
||||||
|
|
||||||
|
@ -3,14 +3,17 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components import ssdp
|
||||||
|
from homeassistant.components.upnp import UpnpDataUpdateCoordinator
|
||||||
from homeassistant.components.upnp.const import (
|
from homeassistant.components.upnp.const import (
|
||||||
CONFIG_ENTRY_ST,
|
CONFIG_ENTRY_ST,
|
||||||
CONFIG_ENTRY_UDN,
|
CONFIG_ENTRY_UDN,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.upnp.device import Device
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .conftest import TEST_ST, TEST_UDN
|
from .conftest import TEST_DISCOVERY, TEST_ST, TEST_UDN
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@ -18,7 +21,6 @@ from tests.common import MockConfigEntry
|
|||||||
@pytest.mark.usefixtures("ssdp_instant_discovery", "mock_get_source_ip")
|
@pytest.mark.usefixtures("ssdp_instant_discovery", "mock_get_source_ip")
|
||||||
async def test_async_setup_entry_default(hass: HomeAssistant):
|
async def test_async_setup_entry_default(hass: HomeAssistant):
|
||||||
"""Test async_setup_entry."""
|
"""Test async_setup_entry."""
|
||||||
|
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
data={
|
data={
|
||||||
@ -30,3 +32,21 @@ async def test_async_setup_entry_default(hass: HomeAssistant):
|
|||||||
# Load config_entry.
|
# Load config_entry.
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
assert await hass.config_entries.async_setup(entry.entry_id) is True
|
assert await hass.config_entries.async_setup(entry.entry_id) is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_reinitialize_device(
|
||||||
|
hass: HomeAssistant, setup_integration: MockConfigEntry
|
||||||
|
):
|
||||||
|
"""Test device is reinitialized when device changes location."""
|
||||||
|
config_entry = setup_integration
|
||||||
|
coordinator: UpnpDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
device: Device = coordinator.device
|
||||||
|
assert device._igd_device.device.device_url == TEST_DISCOVERY.ssdp_location
|
||||||
|
|
||||||
|
# Reinit.
|
||||||
|
new_location = "http://192.168.1.1:12345/desc.xml"
|
||||||
|
headers = {
|
||||||
|
ssdp.ATTR_SSDP_LOCATION: new_location,
|
||||||
|
}
|
||||||
|
await device.async_ssdp_callback(headers, ...)
|
||||||
|
assert device._igd_device.device.device_url == new_location
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""Tests for UPnP/IGD sensor."""
|
"""Tests for UPnP/IGD sensor."""
|
||||||
|
|
||||||
from datetime import timedelta
|
from unittest.mock import patch
|
||||||
from unittest.mock import AsyncMock
|
|
||||||
|
|
||||||
|
from homeassistant.components.upnp import UpnpDataUpdateCoordinator
|
||||||
from homeassistant.components.upnp.const import (
|
from homeassistant.components.upnp.const import (
|
||||||
BYTES_RECEIVED,
|
BYTES_RECEIVED,
|
||||||
BYTES_SENT,
|
BYTES_SENT,
|
||||||
@ -18,15 +18,13 @@ from homeassistant.components.upnp.const import (
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .conftest import MockDevice
|
from .conftest import MockIgdDevice
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
async def test_upnp_sensors(hass: HomeAssistant, setup_integration: MockConfigEntry):
|
async def test_upnp_sensors(hass: HomeAssistant, setup_integration: MockConfigEntry):
|
||||||
"""Test normal sensors."""
|
"""Test normal sensors."""
|
||||||
mock_device: MockDevice = hass.data[DOMAIN][setup_integration.entry_id].device
|
|
||||||
|
|
||||||
# First poll.
|
# First poll.
|
||||||
b_received_state = hass.states.get("sensor.mock_name_b_received")
|
b_received_state = hass.states.get("sensor.mock_name_b_received")
|
||||||
b_sent_state = hass.states.get("sensor.mock_name_b_sent")
|
b_sent_state = hass.states.get("sensor.mock_name_b_sent")
|
||||||
@ -42,23 +40,21 @@ async def test_upnp_sensors(hass: HomeAssistant, setup_integration: MockConfigEn
|
|||||||
assert wan_status_state.state == "Connected"
|
assert wan_status_state.state == "Connected"
|
||||||
|
|
||||||
# Second poll.
|
# Second poll.
|
||||||
mock_device.async_get_traffic_data = AsyncMock(
|
mock_device: MockIgdDevice = hass.data[DOMAIN][
|
||||||
return_value={
|
setup_integration.entry_id
|
||||||
TIMESTAMP: mock_device._timestamp + UPDATE_INTERVAL,
|
].device._igd_device
|
||||||
BYTES_RECEIVED: 10240,
|
mock_device.traffic_data = {
|
||||||
BYTES_SENT: 20480,
|
BYTES_RECEIVED: 10240,
|
||||||
PACKETS_RECEIVED: 30,
|
BYTES_SENT: 20480,
|
||||||
PACKETS_SENT: 40,
|
PACKETS_RECEIVED: 30,
|
||||||
}
|
PACKETS_SENT: 40,
|
||||||
)
|
}
|
||||||
mock_device.async_get_status = AsyncMock(
|
mock_device.status_data = {
|
||||||
return_value={
|
WAN_STATUS: "Disconnected",
|
||||||
WAN_STATUS: "Disconnected",
|
ROUTER_UPTIME: 100,
|
||||||
ROUTER_UPTIME: 100,
|
ROUTER_IP: "",
|
||||||
ROUTER_IP: "",
|
}
|
||||||
}
|
async_fire_time_changed(hass, dt_util.utcnow() + UPDATE_INTERVAL)
|
||||||
)
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=31))
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
b_received_state = hass.states.get("sensor.mock_name_b_received")
|
b_received_state = hass.states.get("sensor.mock_name_b_received")
|
||||||
@ -79,7 +75,9 @@ async def test_derived_upnp_sensors(
|
|||||||
hass: HomeAssistant, setup_integration: MockConfigEntry
|
hass: HomeAssistant, setup_integration: MockConfigEntry
|
||||||
):
|
):
|
||||||
"""Test derived sensors."""
|
"""Test derived sensors."""
|
||||||
mock_device: MockDevice = hass.data[DOMAIN][setup_integration.entry_id].device
|
coordinator: UpnpDataUpdateCoordinator = hass.data[DOMAIN][
|
||||||
|
setup_integration.entry_id
|
||||||
|
]
|
||||||
|
|
||||||
# First poll.
|
# First poll.
|
||||||
kib_s_received_state = hass.states.get("sensor.mock_name_kib_s_received")
|
kib_s_received_state = hass.states.get("sensor.mock_name_kib_s_received")
|
||||||
@ -92,23 +90,28 @@ async def test_derived_upnp_sensors(
|
|||||||
assert packets_s_sent_state.state == "unknown"
|
assert packets_s_sent_state.state == "unknown"
|
||||||
|
|
||||||
# Second poll.
|
# Second poll.
|
||||||
mock_device.async_get_traffic_data = AsyncMock(
|
now = coordinator.data[TIMESTAMP]
|
||||||
return_value={
|
with patch(
|
||||||
TIMESTAMP: mock_device._timestamp + UPDATE_INTERVAL,
|
"homeassistant.components.upnp.device.utcnow",
|
||||||
|
return_value=now + UPDATE_INTERVAL,
|
||||||
|
):
|
||||||
|
mock_device: MockIgdDevice = coordinator.device._igd_device
|
||||||
|
mock_device.traffic_data = {
|
||||||
BYTES_RECEIVED: int(10240 * UPDATE_INTERVAL.total_seconds()),
|
BYTES_RECEIVED: int(10240 * UPDATE_INTERVAL.total_seconds()),
|
||||||
BYTES_SENT: int(20480 * UPDATE_INTERVAL.total_seconds()),
|
BYTES_SENT: int(20480 * UPDATE_INTERVAL.total_seconds()),
|
||||||
PACKETS_RECEIVED: int(30 * UPDATE_INTERVAL.total_seconds()),
|
PACKETS_RECEIVED: int(30 * UPDATE_INTERVAL.total_seconds()),
|
||||||
PACKETS_SENT: int(40 * UPDATE_INTERVAL.total_seconds()),
|
PACKETS_SENT: int(40 * UPDATE_INTERVAL.total_seconds()),
|
||||||
}
|
}
|
||||||
)
|
async_fire_time_changed(hass, now + UPDATE_INTERVAL)
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=31))
|
await hass.async_block_till_done()
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
kib_s_received_state = hass.states.get("sensor.mock_name_kib_s_received")
|
kib_s_received_state = hass.states.get("sensor.mock_name_kib_s_received")
|
||||||
kib_s_sent_state = hass.states.get("sensor.mock_name_kib_s_sent")
|
kib_s_sent_state = hass.states.get("sensor.mock_name_kib_s_sent")
|
||||||
packets_s_received_state = hass.states.get("sensor.mock_name_packets_s_received")
|
packets_s_received_state = hass.states.get(
|
||||||
packets_s_sent_state = hass.states.get("sensor.mock_name_packets_s_sent")
|
"sensor.mock_name_packets_s_received"
|
||||||
assert kib_s_received_state.state == "10.0"
|
)
|
||||||
assert kib_s_sent_state.state == "20.0"
|
packets_s_sent_state = hass.states.get("sensor.mock_name_packets_s_sent")
|
||||||
assert packets_s_received_state.state == "30.0"
|
assert kib_s_received_state.state == "10.0"
|
||||||
assert packets_s_sent_state.state == "40.0"
|
assert kib_s_sent_state.state == "20.0"
|
||||||
|
assert packets_s_received_state.state == "30.0"
|
||||||
|
assert packets_s_sent_state.state == "40.0"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user