Improve test coverage for AVM Fritz!Smarthome (#122974)

This commit is contained in:
Michael 2024-08-09 20:23:00 +02:00 committed by GitHub
parent 65f33f58e9
commit ac28d34ad5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 115 additions and 14 deletions

View file

@ -72,11 +72,8 @@ class FritzboxLight(FritzBoxDeviceEntity, LightEntity):
return self.data.level # type: ignore [no-any-return] return self.data.level # type: ignore [no-any-return]
@property @property
def hs_color(self) -> tuple[float, float] | None: def hs_color(self) -> tuple[float, float]:
"""Return the hs color value.""" """Return the hs color value."""
if self.data.color_mode != COLOR_MODE:
return None
hue = self.data.hue hue = self.data.hue
saturation = self.data.saturation saturation = self.data.saturation

View file

@ -115,6 +115,13 @@ class FritzDeviceClimateMock(FritzEntityBaseMock):
scheduled_preset = PRESET_ECO scheduled_preset = PRESET_ECO
class FritzDeviceClimateWithoutTempSensorMock(FritzDeviceClimateMock):
"""Mock of a AVM Fritz!Box climate device without exposing temperature sensor."""
temperature = None
has_temperature_sensor = False
class FritzDeviceSensorMock(FritzEntityBaseMock): class FritzDeviceSensorMock(FritzEntityBaseMock):
"""Mock of a AVM Fritz!Box sensor device.""" """Mock of a AVM Fritz!Box sensor device."""
@ -187,3 +194,9 @@ class FritzDeviceCoverMock(FritzEntityBaseMock):
has_thermostat = False has_thermostat = False
has_blind = True has_blind = True
levelpercentage = 0 levelpercentage = 0
class FritzDeviceCoverUnknownPositionMock(FritzDeviceCoverMock):
"""Mock of a AVM Fritz!Box cover device with unknown position."""
levelpercentage = None

View file

@ -46,7 +46,12 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from . import FritzDeviceClimateMock, set_devices, setup_config_entry from . import (
FritzDeviceClimateMock,
FritzDeviceClimateWithoutTempSensorMock,
set_devices,
setup_config_entry,
)
from .const import CONF_FAKE_NAME, MOCK_CONFIG from .const import CONF_FAKE_NAME, MOCK_CONFIG
from tests.common import async_fire_time_changed from tests.common import async_fire_time_changed
@ -162,6 +167,18 @@ async def test_setup(hass: HomeAssistant, fritz: Mock) -> None:
assert state.state == PRESET_COMFORT assert state.state == PRESET_COMFORT
async def test_hkr_wo_temperature_sensor(hass: HomeAssistant, fritz: Mock) -> None:
"""Test hkr without exposing dedicated temperature sensor data block."""
device = FritzDeviceClimateWithoutTempSensorMock()
assert await setup_config_entry(
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
)
state = hass.states.get(ENTITY_ID)
assert state
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 18.0
async def test_target_temperature_on(hass: HomeAssistant, fritz: Mock) -> None: async def test_target_temperature_on(hass: HomeAssistant, fritz: Mock) -> None:
"""Test turn device on.""" """Test turn device on."""
device = FritzDeviceClimateMock() device = FritzDeviceClimateMock()

View file

@ -3,7 +3,12 @@
from datetime import timedelta from datetime import timedelta
from unittest.mock import Mock, call from unittest.mock import Mock, call
from homeassistant.components.cover import ATTR_CURRENT_POSITION, ATTR_POSITION, DOMAIN from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
ATTR_POSITION,
DOMAIN,
STATE_OPEN,
)
from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@ -12,11 +17,17 @@ from homeassistant.const import (
SERVICE_OPEN_COVER, SERVICE_OPEN_COVER,
SERVICE_SET_COVER_POSITION, SERVICE_SET_COVER_POSITION,
SERVICE_STOP_COVER, SERVICE_STOP_COVER,
STATE_UNKNOWN,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from . import FritzDeviceCoverMock, set_devices, setup_config_entry from . import (
FritzDeviceCoverMock,
FritzDeviceCoverUnknownPositionMock,
set_devices,
setup_config_entry,
)
from .const import CONF_FAKE_NAME, MOCK_CONFIG from .const import CONF_FAKE_NAME, MOCK_CONFIG
from tests.common import async_fire_time_changed from tests.common import async_fire_time_changed
@ -33,9 +44,22 @@ async def test_setup(hass: HomeAssistant, fritz: Mock) -> None:
state = hass.states.get(ENTITY_ID) state = hass.states.get(ENTITY_ID)
assert state assert state
assert state.state == STATE_OPEN
assert state.attributes[ATTR_CURRENT_POSITION] == 100 assert state.attributes[ATTR_CURRENT_POSITION] == 100
async def test_unknown_position(hass: HomeAssistant, fritz: Mock) -> None:
"""Test cover with unknown position."""
device = FritzDeviceCoverUnknownPositionMock()
assert await setup_config_entry(
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
)
state = hass.states.get(ENTITY_ID)
assert state
assert state.state == STATE_UNKNOWN
async def test_open_cover(hass: HomeAssistant, fritz: Mock) -> None: async def test_open_cover(hass: HomeAssistant, fritz: Mock) -> None:
"""Test opening the cover.""" """Test opening the cover."""
device = FritzDeviceCoverMock() device = FritzDeviceCoverMock()

View file

@ -18,6 +18,7 @@ from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_PASSWORD, CONF_PASSWORD,
CONF_USERNAME, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP,
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
UnitOfTemperature, UnitOfTemperature,
) )
@ -199,6 +200,35 @@ async def test_unload_remove(hass: HomeAssistant, fritz: Mock) -> None:
assert state is None assert state is None
async def test_logout_on_stop(hass: HomeAssistant, fritz: Mock) -> None:
"""Test we log out from fritzbox when Home Assistants stops."""
fritz().get_devices.return_value = [FritzDeviceSwitchMock()]
entity_id = f"{SWITCH_DOMAIN}.{CONF_FAKE_NAME}"
entry = MockConfigEntry(
domain=FB_DOMAIN,
data=MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0],
unique_id=entity_id,
)
entry.add_to_hass(hass)
config_entries = hass.config_entries.async_entries(FB_DOMAIN)
assert len(config_entries) == 1
assert entry is config_entries[0]
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED
state = hass.states.get(entity_id)
assert state
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()
assert fritz().logout.call_count == 1
async def test_remove_device( async def test_remove_device(
hass: HomeAssistant, hass: HomeAssistant,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,

View file

@ -3,6 +3,7 @@
from datetime import timedelta from datetime import timedelta
from unittest.mock import Mock, call from unittest.mock import Mock, call
import pytest
from requests.exceptions import HTTPError from requests.exceptions import HTTPError
from homeassistant.components.fritzbox.const import ( from homeassistant.components.fritzbox.const import (
@ -12,12 +13,14 @@ from homeassistant.components.fritzbox.const import (
) )
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_MODE,
ATTR_COLOR_TEMP_KELVIN, ATTR_COLOR_TEMP_KELVIN,
ATTR_HS_COLOR, ATTR_HS_COLOR,
ATTR_MAX_COLOR_TEMP_KELVIN, ATTR_MAX_COLOR_TEMP_KELVIN,
ATTR_MIN_COLOR_TEMP_KELVIN, ATTR_MIN_COLOR_TEMP_KELVIN,
ATTR_SUPPORTED_COLOR_MODES, ATTR_SUPPORTED_COLOR_MODES,
DOMAIN, DOMAIN,
ColorMode,
) )
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@ -56,9 +59,11 @@ async def test_setup(hass: HomeAssistant, fritz: Mock) -> None:
assert state assert state
assert state.state == STATE_ON assert state.state == STATE_ON
assert state.attributes[ATTR_FRIENDLY_NAME] == "fake_name" assert state.attributes[ATTR_FRIENDLY_NAME] == "fake_name"
assert state.attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP
assert state.attributes[ATTR_COLOR_TEMP_KELVIN] == 2700 assert state.attributes[ATTR_COLOR_TEMP_KELVIN] == 2700
assert state.attributes[ATTR_MIN_COLOR_TEMP_KELVIN] == 2700 assert state.attributes[ATTR_MIN_COLOR_TEMP_KELVIN] == 2700
assert state.attributes[ATTR_MAX_COLOR_TEMP_KELVIN] == 6500 assert state.attributes[ATTR_MAX_COLOR_TEMP_KELVIN] == 6500
assert state.attributes[ATTR_HS_COLOR] == (28.395, 65.723)
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs"] assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs"]
@ -99,6 +104,9 @@ async def test_setup_non_color_non_level(hass: HomeAssistant, fritz: Mock) -> No
assert state.attributes[ATTR_FRIENDLY_NAME] == "fake_name" assert state.attributes[ATTR_FRIENDLY_NAME] == "fake_name"
assert ATTR_BRIGHTNESS not in state.attributes assert ATTR_BRIGHTNESS not in state.attributes
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["onoff"] assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["onoff"]
assert state.attributes[ATTR_COLOR_MODE] == ColorMode.ONOFF
assert state.attributes.get(ATTR_COLOR_TEMP_KELVIN) is None
assert state.attributes.get(ATTR_HS_COLOR) is None
async def test_setup_color(hass: HomeAssistant, fritz: Mock) -> None: async def test_setup_color(hass: HomeAssistant, fritz: Mock) -> None:
@ -120,6 +128,8 @@ async def test_setup_color(hass: HomeAssistant, fritz: Mock) -> None:
assert state assert state
assert state.state == STATE_ON assert state.state == STATE_ON
assert state.attributes[ATTR_FRIENDLY_NAME] == "fake_name" assert state.attributes[ATTR_FRIENDLY_NAME] == "fake_name"
assert state.attributes[ATTR_COLOR_MODE] == ColorMode.HS
assert state.attributes[ATTR_COLOR_TEMP_KELVIN] is None
assert state.attributes[ATTR_BRIGHTNESS] == 100 assert state.attributes[ATTR_BRIGHTNESS] == 100
assert state.attributes[ATTR_HS_COLOR] == (100, 70) assert state.attributes[ATTR_HS_COLOR] == (100, 70)
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs"] assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs"]
@ -183,16 +193,16 @@ async def test_turn_on_color_unsupported_api_method(
device.get_colors.return_value = { device.get_colors.return_value = {
"Red": [("100", "70", "10"), ("100", "50", "10"), ("100", "30", "10")] "Red": [("100", "70", "10"), ("100", "50", "10"), ("100", "30", "10")]
} }
mockresponse = Mock()
mockresponse.status_code = 400
error = HTTPError("Bad Request")
error.response = mockresponse
device.set_unmapped_color.side_effect = error
assert await setup_config_entry( assert await setup_config_entry(
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
) )
# test fallback to `setcolor`
error = HTTPError("Bad Request")
error.response = Mock()
error.response.status_code = 400
device.set_unmapped_color.side_effect = error
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_TURN_ON, SERVICE_TURN_ON,
@ -205,6 +215,16 @@ async def test_turn_on_color_unsupported_api_method(
assert device.set_level.call_args_list == [call(100)] assert device.set_level.call_args_list == [call(100)]
assert device.set_color.call_args_list == [call((100, 70))] assert device.set_color.call_args_list == [call((100, 70))]
# test for unknown error
error.response.status_code = 500
with pytest.raises(HTTPError, match="Bad Request"):
await hass.services.async_call(
DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_BRIGHTNESS: 100, ATTR_HS_COLOR: (100, 70)},
True,
)
async def test_turn_off(hass: HomeAssistant, fritz: Mock) -> None: async def test_turn_off(hass: HomeAssistant, fritz: Mock) -> None:
"""Test turn device off.""" """Test turn device off."""