"""Configuration for Velux tests.""" from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch import pytest from pyvlx import ( Blind, DualRollerShutter, ExteriorHeating, Light, OnOffLight, OnOffSwitch, Scene, Window, ) from homeassistant.components.velux import DOMAIN from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PASSWORD, Platform from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry # Fixtures for the config flow tests @pytest.fixture def mock_setup_entry() -> Generator[AsyncMock]: """Override async_setup_entry.""" with patch( "homeassistant.components.velux.async_setup_entry", return_value=True ) as mock_setup_entry: yield mock_setup_entry @pytest.fixture def mock_config_entry() -> MockConfigEntry: """Return a mock config entry (unified fixture for all tests).""" return MockConfigEntry( entry_id="test_entry_id", domain=DOMAIN, title="127.0.0.1", data={ CONF_HOST: "127.0.0.1", CONF_PASSWORD: "NotAStrongPassword", }, ) @pytest.fixture def mock_discovered_config_entry() -> MockConfigEntry: """Return the user config entry.""" return MockConfigEntry( domain=DOMAIN, title="127.0.0.1", data={ CONF_HOST: "127.0.0.1", CONF_PASSWORD: "NotAStrongPassword", CONF_MAC: "64:61:84:00:ab:cd", }, unique_id="VELUX_KLF_ABCD", ) # various types of fixtures for specific node types # first the window @pytest.fixture def mock_window() -> AsyncMock: """Create a mock Velux window with a rain sensor.""" window = AsyncMock(spec=Window, autospec=True) window.node_id = 1 window.name = "Test Window" window.rain_sensor = True window.serial_number = "123456789" window.get_limitation_min.return_value = MagicMock(position_percent=0) window.get_limitation_max.return_value = MagicMock(position_percent=100) window.set_position_limitations = AsyncMock() window.device_updated_cbs = [] window.is_opening = False window.is_closing = False window.position = MagicMock(position_percent=30, closed=False, known=True) window.wink = AsyncMock() window.pyvlx = MagicMock() return window # a dual roller shutter @pytest.fixture def mock_dual_roller_shutter() -> AsyncMock: """Create a mock Velux dual roller shutter.""" cover = AsyncMock(spec=DualRollerShutter, autospec=True) cover.node_id = 2 cover.name = "Test Dual Roller Shutter" cover.serial_number = "987654321" cover.is_opening = False cover.is_closing = False cover.position_upper_curtain = MagicMock( position_percent=30, closed=False, known=True ) cover.position_lower_curtain = MagicMock( position_percent=30, closed=False, known=True ) cover.position = MagicMock(position_percent=30, closed=False, known=True) cover.get_limitation_min.return_value = MagicMock(position_percent=0) cover.get_limitation_max.return_value = MagicMock(position_percent=100) cover.set_position_limitations = AsyncMock() cover.pyvlx = MagicMock() return cover # a blind @pytest.fixture def mock_blind() -> AsyncMock: """Create a mock Velux blind (cover with tilt).""" blind = AsyncMock(spec=Blind, autospec=True) blind.node_id = 3 blind.name = "Test Blind" blind.serial_number = "4711" # Standard cover position (used by current_cover_position) blind.position = MagicMock(position_percent=40, closed=False, known=True) blind.is_opening = False blind.is_closing = False # Orientation/tilt-related attributes and methods blind.orientation = MagicMock(position_percent=25, known=True) blind.open_orientation = AsyncMock() blind.close_orientation = AsyncMock() blind.stop_orientation = AsyncMock() blind.set_orientation = AsyncMock() blind.get_limitation_min.return_value = MagicMock(position_percent=0) blind.get_limitation_max.return_value = MagicMock(position_percent=100) blind.set_position_limitations = AsyncMock() blind.pyvlx = MagicMock() return blind # a light @pytest.fixture def mock_light() -> AsyncMock: """Create a mock Velux light.""" light = AsyncMock(spec=Light, autospec=True) light.name = "Test Light" light.serial_number = "0815" light.intensity = MagicMock() light.pyvlx = MagicMock() return light # a light without intensity support (e.g., a simple on/off light) @pytest.fixture def mock_onoff_light() -> AsyncMock: """Create a mock Velux light.""" light = AsyncMock(spec=OnOffLight, autospec=True) light.name = "Test On Off Light" light.serial_number = "0816" light.intensity = MagicMock() light.pyvlx = MagicMock() return light # an exterior heating device @pytest.fixture def mock_exterior_heating() -> AsyncMock: """Create a mock Velux exterior heating device.""" exterior_heating = AsyncMock(spec=ExteriorHeating, autospec=True) exterior_heating.name = "Test Exterior Heating" exterior_heating.serial_number = "1984" exterior_heating.intensity = MagicMock(intensity_percent=33) exterior_heating.pyvlx = MagicMock() return exterior_heating # an on/off switch @pytest.fixture def mock_onoff_switch() -> AsyncMock: """Create a mock Velux on/off switch.""" switch = AsyncMock(spec=OnOffSwitch, autospec=True) switch.name = "Test On Off Switch" switch.serial_number = "0817" switch.is_on.return_value = False switch.is_off.return_value = True switch.pyvlx = MagicMock() return switch # fixture to create all other cover types via parameterization @pytest.fixture def mock_cover_type(request: pytest.FixtureRequest) -> AsyncMock: """Create a mock Velux cover of specified type.""" cover = AsyncMock(spec=request.param, autospec=True) cover.node_id = 10 cover.name = f"Test {request.param.__name__}" cover.serial_number = f"serial_{request.param.__name__}" cover.is_opening = False cover.is_closing = False cover.rain_sensor = False cover.position = MagicMock(position_percent=30, closed=False, known=True) cover.position_upper_curtain = MagicMock( position_percent=30, closed=False, known=True ) cover.position_lower_curtain = MagicMock( position_percent=30, closed=False, known=True ) cover.get_limitation_min.return_value = MagicMock(position_percent=0) cover.get_limitation_max.return_value = MagicMock(position_percent=100) cover.set_position_limitations = AsyncMock() cover.pyvlx = MagicMock() return cover @pytest.fixture def mock_pyvlx( mock_scene: AsyncMock, mock_light: AsyncMock, mock_onoff_light: AsyncMock, mock_onoff_switch: AsyncMock, mock_window: AsyncMock, mock_blind: AsyncMock, mock_exterior_heating: AsyncMock, mock_dual_roller_shutter: AsyncMock, request: pytest.FixtureRequest, ) -> Generator[MagicMock]: """Create the library mock and patch PyVLX in both component and config_flow. Tests can parameterize this fixture with the name of a node fixture to include (e.g., "mock_window", "mock_blind", "mock_light", or "mock_cover_type"). If no parameter is provided, an empty node list is used. """ pyvlx = MagicMock() if hasattr(request, "param"): pyvlx.nodes = [request.getfixturevalue(request.param)] else: pyvlx.nodes = [ mock_dual_roller_shutter, mock_light, mock_onoff_light, mock_onoff_switch, mock_blind, mock_window, mock_exterior_heating, ] pyvlx.scenes = [mock_scene] # Async methods invoked by the integration/config flow pyvlx.load_scenes = AsyncMock() pyvlx.load_nodes = AsyncMock() pyvlx.connect = AsyncMock() pyvlx.ensure_connected = AsyncMock() pyvlx.disconnect = AsyncMock() with ( patch("homeassistant.components.velux.PyVLX", return_value=pyvlx), patch("homeassistant.components.velux.config_flow.PyVLX", return_value=pyvlx), ): yield pyvlx @pytest.fixture def mock_scene() -> AsyncMock: """Create a mock Velux scene.""" scene = AsyncMock(spec=Scene, autospec=True) scene.name = "Test Scene" scene.scene_id = "1234" scene.scene = AsyncMock() return scene # Fixture to set up the integration for testing, needs platform fixture, # to be defined in each test file @pytest.fixture async def setup_integration( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_pyvlx: AsyncMock, platform: Platform, ) -> None: """Set up the integration for testing.""" mock_config_entry.add_to_hass(hass) with patch("homeassistant.components.velux.PLATFORMS", [platform]): await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done()