11"""Session fixtures."""
22
3- from collections .abc import Generator
3+ from collections .abc import Buffer , Generator
44from typing import Any
55from unittest .mock import AsyncMock , patch
66import uuid
1212import pytest
1313
1414from homeassistant .components .bluetooth import BluetoothServiceInfoBleak
15+ from homeassistant .components .eurotronic_cometblue import PLATFORMS
1516from homeassistant .components .eurotronic_cometblue .const import DOMAIN
16- from homeassistant .const import CONF_ADDRESS
17+ from homeassistant .const import CONF_ADDRESS , Platform
18+ from homeassistant .core import HomeAssistant
1719from homeassistant .helpers .device_registry import format_mac
1820
19- from .const import (
21+ from . import (
22+ FIXTURE_DEFAULT_CHARACTERISTICS ,
2023 FIXTURE_DEVICE_NAME ,
21- FIXTURE_GATT_CHARACTERISTICS ,
2224 FIXTURE_MAC ,
2325 FIXTURE_RSSI ,
2426 FIXTURE_SERVICE_UUID ,
2527 FIXTURE_USER_INPUT ,
28+ WRITEABLE_CHARACTERISTICS ,
29+ WRITEABLE_CHARACTERISTICS_ALLOW_UNCHANGED ,
2630)
2731
2832from tests .common import MockConfigEntry
5862)
5963
6064
65+ def _normalize_characteristic (
66+ char_specifier : BleakGATTCharacteristic | int | str | uuid .UUID ,
67+ ) -> uuid .UUID :
68+ """Normalize a characteristic specifier to UUID."""
69+ if not isinstance (char_specifier , (BleakGATTCharacteristic , str , uuid .UUID )):
70+ raise BleakCharacteristicNotFoundError (char_specifier )
71+ if isinstance (char_specifier , BleakGATTCharacteristic ):
72+ char_specifier = char_specifier .uuid
73+ if not isinstance (char_specifier , uuid .UUID ):
74+ char_specifier = uuid .UUID (char_specifier )
75+ return char_specifier
76+
77+
6178class MockCometBlueBleakClient (CometBlueBleakClient ):
6279 """Mock BleakClient."""
6380
81+ characteristics : dict [uuid .UUID , bytearray ] = {}
82+
6483 def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
6584 """Mock init."""
6685 super ().__init__ (* args , ** kwargs )
@@ -94,17 +113,40 @@ async def read_gatt_char(
94113 ** kwargs : Any ,
95114 ) -> bytearray :
96115 """Mock read_gatt_char."""
97- if not isinstance (char_specifier , (BleakGATTCharacteristic , str , uuid .UUID )):
98- raise BleakCharacteristicNotFoundError (char_specifier )
99- if isinstance (char_specifier , BleakGATTCharacteristic ):
100- char_specifier = char_specifier .uuid
101- if not isinstance (char_specifier , uuid .UUID ):
102- char_specifier = uuid .UUID (char_specifier )
116+ char_specifier = _normalize_characteristic (char_specifier )
103117 try :
104- return FIXTURE_GATT_CHARACTERISTICS [char_specifier ]
118+ return bytearray ( self . characteristics [char_specifier ])
105119 except KeyError :
106120 raise BleakCharacteristicNotFoundError (char_specifier )
107121
122+ async def write_gatt_char (
123+ self ,
124+ char_specifier : BleakGATTCharacteristic | int | str | uuid .UUID ,
125+ data : Buffer ,
126+ response : bool | None = None ,
127+ ) -> None :
128+ """Mock write_gatt_char."""
129+ char_specifier = _normalize_characteristic (char_specifier )
130+ if char_specifier not in WRITEABLE_CHARACTERISTICS :
131+ raise BleakCharacteristicNotFoundError (char_specifier )
132+ data = bytearray (data )
133+ # when writing temperature it is possible that 128 will be sent, meaning "no change"
134+ # we have to restore the original value in this case to keep tests working
135+ if char_specifier in WRITEABLE_CHARACTERISTICS_ALLOW_UNCHANGED :
136+ for i , byte in enumerate (data ):
137+ if byte == 128 :
138+ data [i ] = self .characteristics [char_specifier ][i ]
139+ self .characteristics [char_specifier ] = data
140+
141+
142+ @pytest .fixture
143+ def mock_gatt_characteristics () -> dict [uuid .UUID , bytearray ]:
144+ """Provide a mutable per-test GATT characteristic store."""
145+ return {
146+ characteristic : bytearray (value )
147+ for characteristic , value in FIXTURE_DEFAULT_CHARACTERISTICS .items ()
148+ }
149+
108150
109151@pytest .fixture
110152def mock_service_info () -> Generator [None ]:
@@ -133,13 +175,26 @@ def mock_ble_device() -> Generator[None]:
133175
134176
135177@pytest .fixture (autouse = True )
136- def mock_bluetooth (enable_bluetooth : None ) -> Generator [None ]:
178+ def mock_bluetooth (
179+ enable_bluetooth : None ,
180+ mock_gatt_characteristics : dict [uuid .UUID , bytearray ],
181+ ) -> Generator [None ]:
137182 """Auto mock bluetooth."""
138183
139- with patch (
140- "eurotronic_cometblue_ha.CometBlueBleakClient" , MockCometBlueBleakClient
184+ MockCometBlueBleakClient .characteristics = mock_gatt_characteristics
185+ with (
186+ patch (
187+ "homeassistant.components.eurotronic_cometblue.entity.bluetooth.async_address_present" ,
188+ return_value = True ,
189+ ),
190+ patch (
191+ "homeassistant.components.eurotronic_cometblue.coordinator.COMMAND_RETRY_INTERVAL" ,
192+ 0 ,
193+ ),
194+ patch ("eurotronic_cometblue_ha.CometBlueBleakClient" , MockCometBlueBleakClient ),
141195 ):
142196 yield
197+ MockCometBlueBleakClient .characteristics = {}
143198
144199
145200# Home Assistant related fixtures
@@ -164,3 +219,16 @@ def mock_setup_entry() -> Generator[AsyncMock]:
164219 return_value = True ,
165220 ) as mock_setup :
166221 yield mock_setup
222+
223+
224+ async def setup_with_selected_platforms (
225+ hass : HomeAssistant , entry : MockConfigEntry , platforms : list [Platform ] | None = None
226+ ) -> None :
227+ """Set up the Eurotronic Comet Blue integration with the selected platforms."""
228+ entry .add_to_hass (hass )
229+ with patch (
230+ "homeassistant.components.eurotronic_cometblue.PLATFORMS" ,
231+ platforms or PLATFORMS ,
232+ ):
233+ assert await hass .config_entries .async_setup (entry .entry_id )
234+ await hass .async_block_till_done ()
0 commit comments