hass-core/tests/components/dlna_dmr/conftest.py

142 lines
4.8 KiB
Python
Raw Normal View History

Config-flow for DLNA-DMR integration (#55267) * Modernize dlna_dmr component: configflow, test, types * Support config-flow with ssdp discovery * Add unit tests * Enforce strict typing * Gracefully handle network devices (dis)appearing * Fix Aiohttp mock response headers type to match actual response class * Fixes from code review * Fixes from code review * Import device config in flow if unavailable at hass start * Support SSDP advertisements * Ignore bad BOOTID, fix ssdp:byebye handling * Only listen for events on interface connected to device * Release all listeners when entities are removed * Warn about deprecated dlna_dmr configuration * Use sublogger for dlna_dmr.config_flow for easier filtering * Tests for dlna_dmr.data module * Rewrite DMR tests for HA style * Fix DMR strings: "Digital Media *Renderer*" * Update DMR entity state and device info when changed * Replace deprecated async_upnp_client State with TransportState * supported_features are dynamic, based on current device state * Cleanup fully when subscription fails * Log warnings when device connection fails unexpectedly * Set PARALLEL_UPDATES to unlimited * Fix spelling * Fixes from code review * Simplify has & can checks to just can, which includes has * Treat transitioning state as playing (not idle) to reduce UI jerking * Test if device is usable * Handle ssdp:update messages properly * Fix _remove_ssdp_callbacks being shared by all DlnaDmrEntity instances * Fix tests for transitioning state * Mock DmrDevice.is_profile_device (added to support embedded devices) * Use ST & NT SSDP headers to find DMR devices, not deviceType The deviceType is extracted from the device's description XML, and will not be what we want when dealing with embedded devices. * Use UDN from SSDP headers, not device description, as unique_id The SSDP headers have the UDN of the embedded device that we're interested in, whereas the device description (`ATTR_UPNP_UDN`) field will always be for the root device. * Fix DMR string English localization * Test config flow with UDN from SSDP headers * Bump async-upnp-client==0.22.1, fix flake8 error * fix test for remapping * DMR HA Device connections based on root and embedded UDN * DmrDevice's UpnpDevice is now named profile_device * Use device type from SSDP headers, not device description * Mark dlna_dmr constants as Final * Use embedded device UDN and type for unique ID when connected via URL * More informative connection error messages * Also match SSDP messages on NT headers The NT header is to ssdp:alive messages what ST is to M-SEARCH responses. * Bump async-upnp-client==0.22.2 * fix merge * Bump async-upnp-client==0.22.3 Co-authored-by: Steven Looman <steven.looman@gmail.com> Co-authored-by: J. Nick Koston <nick@koston.org>
2021-09-28 06:47:01 +10:00
"""Fixtures for DLNA tests."""
from __future__ import annotations
from collections.abc import Iterable
from socket import AddressFamily # pylint: disable=no-name-in-module
from unittest.mock import Mock, create_autospec, patch, seal
from async_upnp_client import UpnpDevice, UpnpFactory
import pytest
from homeassistant.components.dlna_dmr.const import DOMAIN as DLNA_DOMAIN
from homeassistant.components.dlna_dmr.data import DlnaDmrData
from homeassistant.const import CONF_DEVICE_ID, CONF_TYPE, CONF_URL
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
MOCK_DEVICE_BASE_URL = "http://192.88.99.4"
MOCK_DEVICE_LOCATION = MOCK_DEVICE_BASE_URL + "/dmr_description.xml"
MOCK_DEVICE_NAME = "Test Renderer Device"
MOCK_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaRenderer:1"
MOCK_DEVICE_UDN = "uuid:7cc6da13-7f5d-4ace-9729-58b275c52f1e"
MOCK_DEVICE_USN = f"{MOCK_DEVICE_UDN}::{MOCK_DEVICE_TYPE}"
LOCAL_IP = "192.88.99.1"
EVENT_CALLBACK_URL = "http://192.88.99.1/notify"
NEW_DEVICE_LOCATION = "http://192.88.99.7" + "/dmr_description.xml"
@pytest.fixture
def domain_data_mock(hass: HomeAssistant) -> Iterable[Mock]:
"""Mock the global data used by this component.
This includes network clients and library object factories. Mocking it
prevents network use.
"""
domain_data = create_autospec(DlnaDmrData, instance=True)
domain_data.upnp_factory = create_autospec(
UpnpFactory, spec_set=True, instance=True
)
upnp_device = create_autospec(UpnpDevice, instance=True)
upnp_device.name = MOCK_DEVICE_NAME
upnp_device.udn = MOCK_DEVICE_UDN
upnp_device.device_url = MOCK_DEVICE_LOCATION
upnp_device.device_type = "urn:schemas-upnp-org:device:MediaRenderer:1"
upnp_device.available = True
upnp_device.parent_device = None
upnp_device.root_device = upnp_device
upnp_device.all_devices = [upnp_device]
seal(upnp_device)
domain_data.upnp_factory.async_create_device.return_value = upnp_device
domain_data.unmigrated_config = {}
with patch.dict(hass.data, {DLNA_DOMAIN: domain_data}):
yield domain_data
# Make sure the event notifiers are released
assert (
domain_data.async_get_event_notifier.await_count
== domain_data.async_release_event_notifier.await_count
)
@pytest.fixture
def config_entry_mock() -> Iterable[MockConfigEntry]:
"""Mock a config entry for this platform."""
mock_entry = MockConfigEntry(
unique_id=MOCK_DEVICE_UDN,
domain=DLNA_DOMAIN,
data={
CONF_URL: MOCK_DEVICE_LOCATION,
CONF_DEVICE_ID: MOCK_DEVICE_UDN,
CONF_TYPE: MOCK_DEVICE_TYPE,
},
title=MOCK_DEVICE_NAME,
options={},
)
yield mock_entry
@pytest.fixture
def dmr_device_mock(domain_data_mock: Mock) -> Iterable[Mock]:
"""Mock the async_upnp_client DMR device, initially connected."""
with patch(
"homeassistant.components.dlna_dmr.media_player.DmrDevice", autospec=True
) as constructor:
device = constructor.return_value
device.on_event = None
device.profile_device = (
domain_data_mock.upnp_factory.async_create_device.return_value
)
device.media_image_url = "http://192.88.99.20:8200/AlbumArt/2624-17620.jpg"
device.udn = "device_udn"
device.manufacturer = "device_manufacturer"
device.model_name = "device_model_name"
device.name = "device_name"
yield device
# Make sure the device is disconnected
assert (
device.async_subscribe_services.await_count
== device.async_unsubscribe_services.await_count
)
assert device.on_event is None
@pytest.fixture(name="skip_notifications", autouse=True)
def skip_notifications_fixture() -> Iterable[None]:
"""Skip notification calls."""
with patch("homeassistant.components.persistent_notification.async_create"), patch(
"homeassistant.components.persistent_notification.async_dismiss"
):
yield
@pytest.fixture(autouse=True)
def ssdp_scanner_mock() -> Iterable[Mock]:
"""Mock the SSDP module."""
with patch("homeassistant.components.ssdp.Scanner", autospec=True) as mock_scanner:
reg_callback = mock_scanner.return_value.async_register_callback
reg_callback.return_value = Mock(return_value=None)
yield mock_scanner.return_value
assert (
reg_callback.call_count == reg_callback.return_value.call_count
), "Not all callbacks unregistered"
@pytest.fixture(autouse=True)
def async_get_local_ip_mock() -> Iterable[Mock]:
"""Mock the async_get_local_ip utility function to prevent network access."""
with patch(
"homeassistant.components.dlna_dmr.media_player.async_get_local_ip",
autospec=True,
) as func:
func.return_value = AddressFamily.AF_INET, LOCAL_IP
yield func