1
0
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:
David Bishop
2026-04-09 14:45:37 -07:00
committed by GitHub
parent 86b5efaf2c
commit 910dcb4d68
@@ -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."""