mirror of
https://github.com/home-assistant/core.git
synced 2026-05-08 17:49:37 +01:00
Govee light local availability test cleanup (#167702)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
"""Test Govee light local."""
|
||||
|
||||
from errno import EADDRINUSE, ENETDOWN
|
||||
from unittest.mock import AsyncMock, MagicMock, call, patch
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, call, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from govee_local_api import GoveeDevice
|
||||
from govee_local_api.light_capabilities import ON_OFF_CAPABILITIES
|
||||
from govee_local_api.message import DevStatusResponse
|
||||
import pytest
|
||||
|
||||
@@ -16,6 +18,7 @@ from homeassistant.components.govee_light_local.const import (
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_BRIGHTNESS_PCT,
|
||||
ATTR_COLOR_MODE,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
ATTR_EFFECT,
|
||||
ATTR_RGB_COLOR,
|
||||
@@ -84,7 +87,7 @@ async def test_light_unknown_device(
|
||||
ip="192.168.1.101",
|
||||
fingerprint="unkown_device",
|
||||
sku="XYZK",
|
||||
capabilities=None,
|
||||
capabilities=ON_OFF_CAPABILITIES,
|
||||
)
|
||||
]
|
||||
|
||||
@@ -194,7 +197,7 @@ async def test_light_setup_error(
|
||||
assert entry.state is ConfigEntryState.SETUP_ERROR
|
||||
|
||||
|
||||
async def test_light_on_off(hass: HomeAssistant, mock_govee_api: MagicMock) -> None:
|
||||
async def test_light_on_off(hass: HomeAssistant, mock_govee_api: AsyncMock) -> None:
|
||||
"""Test light on and then off."""
|
||||
|
||||
mock_govee_api.devices = [
|
||||
@@ -269,12 +272,12 @@ async def test_light_on_off(hass: HomeAssistant, mock_govee_api: MagicMock) -> N
|
||||
)
|
||||
async def test_turn_on_call_order(
|
||||
hass: HomeAssistant,
|
||||
mock_govee_api: MagicMock,
|
||||
mock_govee_api: AsyncMock,
|
||||
attribute: str,
|
||||
value: str | int | list[int],
|
||||
mock_call: str,
|
||||
mock_call_args: list[str],
|
||||
mock_call_kwargs: dict[str, any],
|
||||
mock_call_kwargs: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test that turn_on is called after set_brightness/set_color/set_preset."""
|
||||
mock_govee_api.devices = [
|
||||
@@ -318,7 +321,7 @@ async def test_turn_on_call_order(
|
||||
)
|
||||
|
||||
|
||||
async def test_light_brightness(hass: HomeAssistant, mock_govee_api: MagicMock) -> None:
|
||||
async def test_light_brightness(hass: HomeAssistant, mock_govee_api: AsyncMock) -> None:
|
||||
"""Test changing brightness."""
|
||||
mock_govee_api.devices = [
|
||||
GoveeDevice(
|
||||
@@ -345,7 +348,7 @@ async def test_light_brightness(hass: HomeAssistant, mock_govee_api: MagicMock)
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{"entity_id": light.entity_id, "brightness_pct": 50},
|
||||
{"entity_id": light.entity_id, ATTR_BRIGHTNESS_PCT: 50},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -385,8 +388,8 @@ async def test_light_brightness(hass: HomeAssistant, mock_govee_api: MagicMock)
|
||||
mock_govee_api.set_brightness.assert_awaited_with(mock_govee_api.devices[0], 100)
|
||||
|
||||
|
||||
async def test_light_color(hass: HomeAssistant, mock_govee_api: MagicMock) -> None:
|
||||
"""Test changing brightness."""
|
||||
async def test_light_color(hass: HomeAssistant, mock_govee_api: AsyncMock) -> None:
|
||||
"""Test changing color."""
|
||||
mock_govee_api.devices = [
|
||||
GoveeDevice(
|
||||
controller=mock_govee_api,
|
||||
@@ -421,7 +424,7 @@ async def test_light_color(hass: HomeAssistant, mock_govee_api: MagicMock) -> No
|
||||
assert light is not None
|
||||
assert light.state == "on"
|
||||
assert light.attributes[ATTR_RGB_COLOR] == (100, 255, 50)
|
||||
assert light.attributes["color_mode"] == ColorMode.RGB
|
||||
assert light.attributes[ATTR_COLOR_MODE] == ColorMode.RGB
|
||||
|
||||
mock_govee_api.set_color.assert_awaited_with(
|
||||
mock_govee_api.devices[0], rgb=(100, 255, 50), temperature=None
|
||||
@@ -430,7 +433,7 @@ async def test_light_color(hass: HomeAssistant, mock_govee_api: MagicMock) -> No
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{"entity_id": light.entity_id, "color_temp_kelvin": 4400},
|
||||
{"entity_id": light.entity_id, ATTR_COLOR_TEMP_KELVIN: 4400},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -438,15 +441,15 @@ async def test_light_color(hass: HomeAssistant, mock_govee_api: MagicMock) -> No
|
||||
light = hass.states.get("light.H615A")
|
||||
assert light is not None
|
||||
assert light.state == "on"
|
||||
assert light.attributes["color_temp_kelvin"] == 4400
|
||||
assert light.attributes["color_mode"] == ColorMode.COLOR_TEMP
|
||||
assert light.attributes[ATTR_COLOR_TEMP_KELVIN] == 4400
|
||||
assert light.attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP
|
||||
|
||||
mock_govee_api.set_color.assert_awaited_with(
|
||||
mock_govee_api.devices[0], rgb=None, temperature=4400
|
||||
)
|
||||
|
||||
|
||||
async def test_scene_on(hass: HomeAssistant, mock_govee_api: MagicMock) -> None:
|
||||
async def test_scene_on(hass: HomeAssistant, mock_govee_api: AsyncMock) -> None:
|
||||
"""Test turning on scene."""
|
||||
|
||||
mock_govee_api.devices = [
|
||||
@@ -487,7 +490,7 @@ async def test_scene_on(hass: HomeAssistant, mock_govee_api: MagicMock) -> None:
|
||||
|
||||
|
||||
async def test_scene_restore_rgb(
|
||||
hass: HomeAssistant, mock_govee_api: MagicMock
|
||||
hass: HomeAssistant, mock_govee_api: AsyncMock
|
||||
) -> None:
|
||||
"""Test restore rgb color."""
|
||||
|
||||
@@ -570,7 +573,7 @@ async def test_scene_restore_rgb(
|
||||
|
||||
|
||||
async def test_scene_restore_temperature(
|
||||
hass: HomeAssistant, mock_govee_api: MagicMock
|
||||
hass: HomeAssistant, mock_govee_api: AsyncMock
|
||||
) -> None:
|
||||
"""Test restore color temperature."""
|
||||
|
||||
@@ -601,7 +604,7 @@ async def test_scene_restore_temperature(
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{"entity_id": light.entity_id, "color_temp_kelvin": initial_color},
|
||||
{"entity_id": light.entity_id, ATTR_COLOR_TEMP_KELVIN: initial_color},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -609,7 +612,7 @@ async def test_scene_restore_temperature(
|
||||
light = hass.states.get("light.H615A")
|
||||
assert light is not None
|
||||
assert light.state == "on"
|
||||
assert light.attributes["color_temp_kelvin"] == initial_color
|
||||
assert light.attributes[ATTR_COLOR_TEMP_KELVIN] == initial_color
|
||||
mock_govee_api.turn_on_off.assert_awaited_with(mock_govee_api.devices[0], True)
|
||||
|
||||
# Activate scene
|
||||
@@ -640,11 +643,11 @@ async def test_scene_restore_temperature(
|
||||
assert light is not None
|
||||
assert light.state == "on"
|
||||
assert light.attributes[ATTR_EFFECT] is None
|
||||
assert light.attributes["color_temp_kelvin"] == initial_color
|
||||
assert light.attributes[ATTR_COLOR_TEMP_KELVIN] == initial_color
|
||||
|
||||
|
||||
async def test_update_callback_registered_and_triggers_state_update(
|
||||
hass: HomeAssistant, mock_govee_api: MagicMock
|
||||
hass: HomeAssistant, mock_govee_api: AsyncMock
|
||||
) -> None:
|
||||
"""Test that update callback is registered and triggers state update."""
|
||||
device = GoveeDevice(
|
||||
@@ -679,7 +682,7 @@ async def test_update_callback_registered_and_triggers_state_update(
|
||||
|
||||
|
||||
async def test_update_callback_cleared_on_remove(
|
||||
hass: HomeAssistant, mock_govee_api: MagicMock
|
||||
hass: HomeAssistant, mock_govee_api: AsyncMock
|
||||
) -> None:
|
||||
"""Test that update callback is cleared when entity is removed."""
|
||||
device = GoveeDevice(
|
||||
@@ -705,7 +708,7 @@ async def test_update_callback_cleared_on_remove(
|
||||
assert device.update_callback is None
|
||||
|
||||
|
||||
async def test_scene_none(hass: HomeAssistant, mock_govee_api: MagicMock) -> None:
|
||||
async def test_scene_none(hass: HomeAssistant, mock_govee_api: AsyncMock) -> None:
|
||||
"""Test turn on 'none' scene."""
|
||||
|
||||
mock_govee_api.devices = [
|
||||
@@ -794,12 +797,17 @@ def _status_response(
|
||||
)
|
||||
|
||||
|
||||
async def test_device_becomes_unavailable_after_timeout(
|
||||
async def test_device_availability(
|
||||
hass: HomeAssistant,
|
||||
mock_govee_api: MagicMock,
|
||||
mock_govee_api: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test that a device goes unavailable when no status response arrives."""
|
||||
"""Test device availability tracks lastseen against DEVICE_TIMEOUT.
|
||||
|
||||
Walks the full timeline in a single fixture: stays available below the
|
||||
timeout, goes unavailable past it, and recovers when a status response
|
||||
refreshes ``lastseen``.
|
||||
"""
|
||||
device = GoveeDevice(
|
||||
controller=mock_govee_api,
|
||||
ip="192.168.1.100",
|
||||
@@ -819,9 +827,18 @@ async def test_device_becomes_unavailable_after_timeout(
|
||||
assert state is not None
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# Advance past DEVICE_TIMEOUT without firing any status responses, and
|
||||
# tick the coordinator forward so a state write occurs.
|
||||
freezer.tick(DEVICE_TIMEOUT + SCAN_INTERVAL)
|
||||
# Advance but stay below DEVICE_TIMEOUT: the device must remain available
|
||||
# even though no status responses have arrived.
|
||||
freezer.tick(DEVICE_TIMEOUT - SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass, dt_util.utcnow())
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.H615A")
|
||||
assert state is not None
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# Advance past DEVICE_TIMEOUT: the device should go unavailable.
|
||||
freezer.tick(SCAN_INTERVAL * 2)
|
||||
async_fire_time_changed(hass, dt_util.utcnow())
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -829,38 +846,8 @@ async def test_device_becomes_unavailable_after_timeout(
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_device_recovers_after_status_response(
|
||||
hass: HomeAssistant,
|
||||
mock_govee_api: MagicMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test that an unavailable device recovers when it responds again."""
|
||||
device = GoveeDevice(
|
||||
controller=mock_govee_api,
|
||||
ip="192.168.1.100",
|
||||
fingerprint="asdawdqwdqwd",
|
||||
sku="H615A",
|
||||
capabilities=DEFAULT_CAPABILITIES,
|
||||
)
|
||||
mock_govee_api.devices = [device]
|
||||
|
||||
entry = MockConfigEntry(domain=DOMAIN)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Drive it unavailable first.
|
||||
freezer.tick(DEVICE_TIMEOUT + SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass, dt_util.utcnow())
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.H615A")
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# A status response refreshes lastseen and fires the entity callback.
|
||||
# A status response refreshes lastseen and fires the entity callback, so
|
||||
# the device recovers without waiting for another coordinator poll.
|
||||
device.update(_status_response())
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -871,7 +858,7 @@ async def test_device_recovers_after_status_response(
|
||||
|
||||
async def test_one_silent_device_does_not_affect_others(
|
||||
hass: HomeAssistant,
|
||||
mock_govee_api: MagicMock,
|
||||
mock_govee_api: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test that one silent device does not pull the others unavailable."""
|
||||
|
||||
Reference in New Issue
Block a user