44from unittest .mock import AsyncMock , MagicMock , patch
55
66import pytest
7+ from pyvlx .lightening_device import LighteningDevice
8+ from pyvlx .opening_device import Blind , Window
79
810from homeassistant .components .velux import DOMAIN
9- from homeassistant .components .velux .binary_sensor import Window
10- from homeassistant .components .velux .light import LighteningDevice
1111from homeassistant .components .velux .scene import PyVLXScene as Scene
1212from homeassistant .const import CONF_HOST , CONF_MAC , CONF_PASSWORD , Platform
1313from homeassistant .core import HomeAssistant
@@ -26,22 +26,10 @@ def mock_setup_entry() -> Generator[AsyncMock]:
2626
2727
2828@pytest .fixture
29- def mock_velux_client () -> Generator [AsyncMock ]:
30- """Mock a Velux client."""
31- with (
32- patch (
33- "homeassistant.components.velux.config_flow.PyVLX" ,
34- autospec = True ,
35- ) as mock_client ,
36- ):
37- client = mock_client .return_value
38- yield client
39-
40-
41- @pytest .fixture
42- def mock_user_config_entry () -> MockConfigEntry :
43- """Return the user config entry."""
29+ def mock_config_entry () -> MockConfigEntry :
30+ """Return a mock config entry (unified fixture for all tests)."""
4431 return MockConfigEntry (
32+ entry_id = "test_entry_id" ,
4533 domain = DOMAIN ,
4634 title = "127.0.0.1" ,
4735 data = {
@@ -66,7 +54,8 @@ def mock_discovered_config_entry() -> MockConfigEntry:
6654 )
6755
6856
69- # fixtures for the binary sensor tests
57+ # various types of fixtures for specific node types
58+ # first the window
7059@pytest .fixture
7160def mock_window () -> AsyncMock :
7261 """Create a mock Velux window with a rain sensor."""
@@ -81,6 +70,27 @@ def mock_window() -> AsyncMock:
8170 return window
8271
8372
73+ # a blind
74+ @pytest .fixture
75+ def mock_blind () -> AsyncMock :
76+ """Create a mock Velux blind (cover with tilt)."""
77+ blind = AsyncMock (spec = Blind , autospec = True )
78+ blind .name = "Test Blind"
79+ blind .serial_number = "4711"
80+ # Standard cover position (used by current_cover_position)
81+ blind .position = MagicMock (position_percent = 40 , closed = False )
82+ blind .is_opening = False
83+ blind .is_closing = False
84+ # Orientation/tilt-related attributes and methods
85+ blind .orientation = MagicMock (position_percent = 25 )
86+ blind .open_orientation = AsyncMock ()
87+ blind .close_orientation = AsyncMock ()
88+ blind .stop_orientation = AsyncMock ()
89+ blind .set_orientation = AsyncMock ()
90+ return blind
91+
92+
93+ # a light
8494@pytest .fixture
8595def mock_light () -> AsyncMock :
8696 """Create a mock Velux light."""
@@ -91,50 +101,72 @@ def mock_light() -> AsyncMock:
91101 return light
92102
93103
104+ # fixture to create all other cover types via parameterization
94105@pytest .fixture
95- def mock_scene () -> AsyncMock :
96- """Create a mock Velux scene."""
97- scene = AsyncMock (spec = Scene , autospec = True )
98- scene .name = "Test Scene"
99- scene .scene_id = "1234"
100- scene .scene = AsyncMock ()
101- return scene
106+ def mock_cover_type (request : pytest .FixtureRequest ) -> AsyncMock :
107+ """Create a mock Velux cover of specified type."""
108+ cover = AsyncMock (spec = request .param , autospec = True )
109+ cover .name = f"Test { request .param .__name__ } "
110+ cover .serial_number = f"serial_{ request .param .__name__ } "
111+ cover .is_opening = False
112+ cover .is_closing = False
113+ cover .position = MagicMock (position_percent = 30 , closed = False )
114+ return cover
102115
103116
104117@pytest .fixture
105118def mock_pyvlx (
106- mock_window : MagicMock , mock_light : MagicMock , mock_scene : AsyncMock
119+ mock_scene : AsyncMock ,
120+ mock_light : AsyncMock ,
121+ mock_window : AsyncMock ,
122+ mock_blind : AsyncMock ,
123+ request : pytest .FixtureRequest ,
107124) -> Generator [MagicMock ]:
108- """Create the library mock and patch PyVLX."""
125+ """Create the library mock and patch PyVLX in both component and config_flow.
126+
127+ Tests can parameterize this fixture with the name of a node fixture to include
128+ (e.g., "mock_window", "mock_blind", "mock_light", or "mock_cover_type").
129+ If no parameter is provided, an empty node list is used.
130+ """
131+
109132 pyvlx = MagicMock ()
110- pyvlx .nodes = [mock_window , mock_light ]
133+
134+ if hasattr (request , "param" ):
135+ pyvlx .nodes = [request .getfixturevalue (request .param )]
136+ else :
137+ pyvlx .nodes = [mock_light , mock_blind , mock_window , mock_cover_type ]
138+
111139 pyvlx .scenes = [mock_scene ]
140+
141+ # Async methods invoked by the integration/config flow
112142 pyvlx .load_scenes = AsyncMock ()
113143 pyvlx .load_nodes = AsyncMock ()
144+ pyvlx .connect = AsyncMock ()
114145 pyvlx .disconnect = AsyncMock ()
115146
116- with patch ("homeassistant.components.velux.PyVLX" , return_value = pyvlx ):
147+ with (
148+ patch ("homeassistant.components.velux.PyVLX" , return_value = pyvlx ),
149+ patch ("homeassistant.components.velux.config_flow.PyVLX" , return_value = pyvlx ),
150+ ):
117151 yield pyvlx
118152
119153
120154@pytest .fixture
121- def mock_config_entry () -> MockConfigEntry :
122- """Return a mock config entry."""
123- return MockConfigEntry (
124- entry_id = "test_entry_id" ,
125- domain = DOMAIN ,
126- data = {
127- CONF_HOST : "testhost" ,
128- CONF_PASSWORD : "testpw" ,
129- },
130- )
155+ def mock_scene () -> AsyncMock :
156+ """Create a mock Velux scene."""
157+ scene = AsyncMock (spec = Scene , autospec = True )
158+ scene .name = "Test Scene"
159+ scene .scene_id = "1234"
160+ scene .scene = AsyncMock ()
161+ return scene
131162
132163
164+ # Fixture to set up the integration for testing, needs platform fixture, to be defined in each test file
133165@pytest .fixture
134166async def setup_integration (
135167 hass : HomeAssistant ,
136168 mock_config_entry : MockConfigEntry ,
137- mock_pyvlx : MagicMock ,
169+ mock_pyvlx : AsyncMock ,
138170 platform : Platform ,
139171) -> None :
140172 """Set up the integration for testing."""
0 commit comments