"""Fixtures for DLNA DMS tests.""" from __future__ import annotations from collections.abc import AsyncIterable, Iterable from typing import Final, cast from unittest.mock import Mock, create_autospec, patch, seal from async_upnp_client.client import UpnpDevice, UpnpService from async_upnp_client.utils import absolute_url import pytest from homeassistant.components.dlna_dms.const import ( CONF_SOURCE_ID, CONFIG_VERSION, DOMAIN, ) from homeassistant.components.dlna_dms.dms import DlnaDmsData from homeassistant.const import CONF_DEVICE_ID, CONF_URL from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry MOCK_DEVICE_HOST: Final = "192.88.99.21" MOCK_DEVICE_BASE_URL: Final = f"http://{MOCK_DEVICE_HOST}" MOCK_DEVICE_LOCATION: Final = MOCK_DEVICE_BASE_URL + "/dms_description.xml" MOCK_DEVICE_NAME: Final = "Test Server Device" MOCK_DEVICE_TYPE: Final = "urn:schemas-upnp-org:device:MediaServer:1" MOCK_DEVICE_UDN: Final = "uuid:7bf34520-f034-4fa2-8d2d-2f709d4221ef" MOCK_DEVICE_USN: Final = f"{MOCK_DEVICE_UDN}::{MOCK_DEVICE_TYPE}" MOCK_SOURCE_ID: Final = "test_server_device" LOCAL_IP: Final = "192.88.99.1" EVENT_CALLBACK_URL: Final = "http://192.88.99.1/notify" NEW_DEVICE_LOCATION: Final = "http://192.88.99.7" + "/dmr_description.xml" @pytest.fixture async def setup_media_source(hass) -> None: """Set up media source.""" assert await async_setup_component(hass, "media_source", {}) @pytest.fixture def upnp_factory_mock() -> Iterable[Mock]: """Mock the UpnpFactory class to construct DMS-style UPnP devices.""" with patch( "homeassistant.components.dlna_dms.dms.UpnpFactory", autospec=True, spec_set=True, ) as upnp_factory: 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 = MOCK_DEVICE_TYPE upnp_device.available = True upnp_device.parent_device = None upnp_device.root_device = upnp_device upnp_device.all_devices = [upnp_device] upnp_device.services = { "urn:schemas-upnp-org:service:ContentDirectory:1": create_autospec( UpnpService, instance=True, service_type="urn:schemas-upnp-org:service:ContentDirectory:1", service_id="urn:upnp-org:serviceId:ContentDirectory", ), "urn:schemas-upnp-org:service:ConnectionManager:1": create_autospec( UpnpService, instance=True, service_type="urn:schemas-upnp-org:service:ConnectionManager:1", service_id="urn:upnp-org:serviceId:ConnectionManager", ), } seal(upnp_device) upnp_factory_instance = upnp_factory.return_value upnp_factory_instance.async_create_device.return_value = upnp_device yield upnp_factory_instance @pytest.fixture(autouse=True, scope="module") def aiohttp_session_requester_mock() -> Iterable[Mock]: """Mock the AiohttpSessionRequester to prevent network use.""" with patch( "homeassistant.components.dlna_dms.dms.AiohttpSessionRequester", autospec=True ) as requester_mock: yield requester_mock @pytest.fixture def config_entry_mock() -> MockConfigEntry: """Mock a config entry for this platform.""" mock_entry = MockConfigEntry( unique_id=MOCK_DEVICE_USN, domain=DOMAIN, version=CONFIG_VERSION, data={ CONF_URL: MOCK_DEVICE_LOCATION, CONF_DEVICE_ID: MOCK_DEVICE_USN, CONF_SOURCE_ID: MOCK_SOURCE_ID, }, title=MOCK_DEVICE_NAME, ) return mock_entry @pytest.fixture def dms_device_mock(upnp_factory_mock: Mock) -> Iterable[Mock]: """Mock the async_upnp_client DMS device, initially connected.""" with patch( "homeassistant.components.dlna_dms.dms.DmsDevice", autospec=True ) as constructor: device = constructor.return_value device.on_event = None device.profile_device = upnp_factory_mock.async_create_device.return_value device.icon = MOCK_DEVICE_BASE_URL + "/icon.jpg" device.udn = "device_udn" device.manufacturer = "device_manufacturer" device.model_name = "device_model_name" device.name = "device_name" device.get_absolute_url.side_effect = lambda url: absolute_url( MOCK_DEVICE_BASE_URL, url ) yield device @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 @pytest.fixture async def device_source_mock( hass: HomeAssistant, config_entry_mock: MockConfigEntry, ssdp_scanner_mock: Mock, dms_device_mock: Mock, ) -> AsyncIterable[None]: """Fixture to set up a DmsDeviceSource in a connected state and cleanup at completion.""" config_entry_mock.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry_mock.entry_id) await hass.async_block_till_done() # Check the DmsDeviceSource has registered all needed listeners assert len(config_entry_mock.update_listeners) == 0 assert ssdp_scanner_mock.async_register_callback.await_count == 2 assert ssdp_scanner_mock.async_register_callback.return_value.call_count == 0 # Run the test yield None # Unload config entry to clean up assert await hass.config_entries.async_remove(config_entry_mock.entry_id) == { "require_restart": False } # Check DmsDeviceSource has cleaned up its resources assert not config_entry_mock.update_listeners assert ( ssdp_scanner_mock.async_register_callback.await_count == ssdp_scanner_mock.async_register_callback.return_value.call_count ) domain_data = cast(DlnaDmsData, hass.data[DOMAIN]) assert MOCK_DEVICE_USN not in domain_data.devices assert MOCK_SOURCE_ID not in domain_data.sources