Refactor Ultima fixtures to reduce duplication (#167731)

This commit is contained in:
TimL
2026-04-09 13:20:01 +02:00
committed by GitHub
parent 8e430d9f26
commit 3ea15f2743
3 changed files with 59 additions and 98 deletions
+13
View File
@@ -124,6 +124,19 @@ def mock_smlight_client(request: pytest.FixtureRequest) -> Generator[MagicMock]:
yield api
MOCK_ULTIMA = Info(
MAC="AA:BB:CC:DD:EE:FF",
model="SLZB-Ultima3",
)
@pytest.fixture
def mock_ultima_client(mock_smlight_client: MagicMock) -> MagicMock:
"""Configure api client to return an Ultima device."""
mock_smlight_client.get_info.side_effect = lambda *arg, **kwargs: MOCK_ULTIMA
return mock_smlight_client
async def setup_integration(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
+8 -30
View File
@@ -3,7 +3,6 @@
from unittest.mock import MagicMock
from infrared_protocols import Command, Timing
from pysmlight import Info
from pysmlight.exceptions import SmlightError
from pysmlight.models import IRPayload
import pytest
@@ -36,20 +35,12 @@ def platforms() -> list[Platform]:
return [Platform.INFRARED]
MOCK_ULTIMA = Info(
MAC="AA:BB:CC:DD:EE:FF",
model="SLZB-Ultima3",
)
async def test_infrared_setup_ultima(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test infrared entity is created for Ultima devices."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
state = hass.states.get("infrared.mock_title_ir_emitter")
@@ -62,11 +53,6 @@ async def test_infrared_not_created_non_ultima(
mock_smlight_client: MagicMock,
) -> None:
"""Test infrared entity is not created for non-Ultima devices."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = Info(
MAC="AA:BB:CC:DD:EE:FF",
model="SLZB-MR1",
)
await setup_integration(hass, mock_config_entry)
state = hass.states.get("infrared.mock_title_ir_emitter")
@@ -76,11 +62,9 @@ async def test_infrared_not_created_non_ultima(
async def test_infrared_send_command(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test sending IR command."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "infrared.mock_title_ir_emitter"
@@ -93,7 +77,7 @@ async def test_infrared_send_command(
MockCommand(),
)
mock_smlight_client.actions.send_ir_code.assert_called_once_with(
mock_ultima_client.actions.send_ir_code.assert_called_once_with(
IRPayload.from_raw_timings([9000, 4500, 560, 1690], freq=38000)
)
@@ -101,18 +85,16 @@ async def test_infrared_send_command(
async def test_infrared_send_command_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test connection error handling."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "infrared.mock_title_ir_emitter"
state = hass.states.get(entity_id)
assert state is not None
mock_smlight_client.actions.send_ir_code.side_effect = SmlightError("Failed")
mock_ultima_client.actions.send_ir_code.side_effect = SmlightError("Failed")
with pytest.raises(HomeAssistantError) as exc_info:
await async_send_command(
@@ -126,18 +108,16 @@ async def test_infrared_send_command_error(
async def test_infrared_send_empty_command_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test ValueError from pysmlight is surfaced as HomeAssistantError."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "infrared.mock_title_ir_emitter"
state = hass.states.get(entity_id)
assert state is not None
mock_smlight_client.actions.send_ir_code.side_effect = ValueError("empty payload")
mock_ultima_client.actions.send_ir_code.side_effect = ValueError("empty payload")
with pytest.raises(HomeAssistantError) as exc_info:
await async_send_command(
@@ -152,11 +132,9 @@ async def test_infrared_send_empty_command_error(
async def test_infrared_state_updated_after_send(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test that entity state is updated with a timestamp after a successful send."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "infrared.mock_title_ir_emitter"
+38 -68
View File
@@ -3,7 +3,6 @@
from collections.abc import Awaitable, Callable
from unittest.mock import MagicMock
from pysmlight import Info
from pysmlight.const import AmbiEffect
from pysmlight.exceptions import SmlightConnectionError
from pysmlight.models import AmbilightPayload
@@ -40,17 +39,11 @@ def platforms() -> Platform | list[Platform]:
return [Platform.LIGHT]
MOCK_ULTIMA = Info(
MAC="AA:BB:CC:DD:EE:FF",
model="SLZB-Ultima3",
)
def _build_fire_sse_ambilight(
hass: HomeAssistant, mock_smlight_client: MagicMock
hass: HomeAssistant, mock_ultima_client: MagicMock
) -> Callable[[dict[str, object]], Awaitable[None]]:
"""Build helper to push ambilight SSE events and wait for state updates."""
page_callback = mock_smlight_client.sse.register_page_cb.call_args[0][1]
page_callback = mock_ultima_client.sse.register_page_cb.call_args[0][1]
async def fire_ambi(changes: dict[str, object]) -> None:
page_callback(changes)
@@ -63,12 +56,10 @@ async def test_light_setup_ultima(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
snapshot: SnapshotAssertion,
) -> None:
"""Test light entity is created for Ultima devices."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
entry = await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
@@ -82,11 +73,6 @@ async def test_light_not_created_non_ultima(
mock_smlight_client: MagicMock,
) -> None:
"""Test light entity is not created for non-Ultima devices."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = Info(
MAC="AA:BB:CC:DD:EE:FF",
model="SLZB-MR1",
)
await setup_integration(hass, mock_config_entry)
state = hass.states.get("light.mock_title_ambilight")
@@ -96,20 +82,18 @@ async def test_light_not_created_non_ultima(
async def test_light_turn_on_off(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test turning light on and off."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "light.mock_title_ambilight"
state = hass.states.get(entity_id)
assert state.state != STATE_UNAVAILABLE
fire_ambi = _build_fire_sse_ambilight(hass, mock_smlight_client)
fire_ambi = _build_fire_sse_ambilight(hass, mock_ultima_client)
mock_smlight_client.actions.ambilight.reset_mock()
mock_ultima_client.actions.ambilight.reset_mock()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
@@ -117,7 +101,7 @@ async def test_light_turn_on_off(
blocking=True,
)
mock_smlight_client.actions.ambilight.assert_called_once_with(
mock_ultima_client.actions.ambilight.assert_called_once_with(
AmbilightPayload(ultLedMode=AmbiEffect.WSULT_SOLID)
)
await fire_ambi({"ultLedMode": 0, "ultLedBri": 158, "ultLedColor": 0x7FACFF})
@@ -125,7 +109,7 @@ async def test_light_turn_on_off(
state = hass.states.get(entity_id)
assert state.state == STATE_ON
mock_smlight_client.actions.ambilight.reset_mock()
mock_ultima_client.actions.ambilight.reset_mock()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
@@ -133,7 +117,7 @@ async def test_light_turn_on_off(
blocking=True,
)
mock_smlight_client.actions.ambilight.assert_called_once_with(
mock_ultima_client.actions.ambilight.assert_called_once_with(
AmbilightPayload(ultLedMode=AmbiEffect.WSULT_OFF)
)
await fire_ambi({"ultLedMode": 1})
@@ -145,20 +129,18 @@ async def test_light_turn_on_off(
async def test_light_brightness(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test setting brightness."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "light.mock_title_ambilight"
fire_ambi = _build_fire_sse_ambilight(hass, mock_smlight_client)
fire_ambi = _build_fire_sse_ambilight(hass, mock_ultima_client)
# Seed current state as on so brightness-only update does not force solid mode.
await fire_ambi({"ultLedMode": 0, "ultLedBri": 158, "ultLedColor": 0x7FACFF})
mock_smlight_client.actions.ambilight.reset_mock()
mock_ultima_client.actions.ambilight.reset_mock()
await hass.services.async_call(
LIGHT_DOMAIN,
@@ -167,7 +149,7 @@ async def test_light_brightness(
blocking=True,
)
mock_smlight_client.actions.ambilight.assert_called_once_with(
mock_ultima_client.actions.ambilight.assert_called_once_with(
AmbilightPayload(ultLedBri=200)
)
await fire_ambi({"ultLedMode": 0, "ultLedBri": 200, "ultLedColor": 0x7FACFF})
@@ -180,18 +162,16 @@ async def test_light_brightness(
async def test_light_rgb_color(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test setting RGB color."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "light.mock_title_ambilight"
fire_ambi = _build_fire_sse_ambilight(hass, mock_smlight_client)
fire_ambi = _build_fire_sse_ambilight(hass, mock_ultima_client)
mock_smlight_client.actions.ambilight.reset_mock()
mock_ultima_client.actions.ambilight.reset_mock()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
@@ -199,7 +179,7 @@ async def test_light_rgb_color(
blocking=True,
)
mock_smlight_client.actions.ambilight.assert_called_once_with(
mock_ultima_client.actions.ambilight.assert_called_once_with(
AmbilightPayload(ultLedMode=AmbiEffect.WSULT_SOLID, ultLedColor="#ff8040")
)
await fire_ambi({"ultLedMode": 0, "ultLedColor": 0xFF8040})
@@ -212,19 +192,17 @@ async def test_light_rgb_color(
async def test_light_effect(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test setting effect."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "light.mock_title_ambilight"
fire_ambi = _build_fire_sse_ambilight(hass, mock_smlight_client)
fire_ambi = _build_fire_sse_ambilight(hass, mock_ultima_client)
# Test Rainbow effect
mock_smlight_client.actions.ambilight.reset_mock()
mock_ultima_client.actions.ambilight.reset_mock()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
@@ -232,13 +210,13 @@ async def test_light_effect(
blocking=True,
)
mock_smlight_client.actions.ambilight.assert_called_once_with(
mock_ultima_client.actions.ambilight.assert_called_once_with(
AmbilightPayload(ultLedMode=AmbiEffect.WSULT_RAINBOW)
)
await fire_ambi({"ultLedMode": 3})
# Test Blur effect
mock_smlight_client.actions.ambilight.reset_mock()
mock_ultima_client.actions.ambilight.reset_mock()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
@@ -246,7 +224,7 @@ async def test_light_effect(
blocking=True,
)
mock_smlight_client.actions.ambilight.assert_called_once_with(
mock_ultima_client.actions.ambilight.assert_called_once_with(
AmbilightPayload(ultLedMode=AmbiEffect.WSULT_BLUR)
)
await fire_ambi({"ultLedMode": 2})
@@ -259,16 +237,14 @@ async def test_light_effect(
async def test_light_invalid_effect(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test handling of invalid effect name is ignored."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "light.mock_title_ambilight"
mock_smlight_client.actions.ambilight.reset_mock()
mock_ultima_client.actions.ambilight.reset_mock()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
@@ -276,25 +252,23 @@ async def test_light_invalid_effect(
blocking=True,
)
mock_smlight_client.actions.ambilight.assert_not_called()
mock_ultima_client.actions.ambilight.assert_not_called()
async def test_light_turn_on_when_on_is_noop(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test calling turn_on with no attributes does nothing when already on."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "light.mock_title_ambilight"
fire_ambi = _build_fire_sse_ambilight(hass, mock_smlight_client)
fire_ambi = _build_fire_sse_ambilight(hass, mock_ultima_client)
await fire_ambi({"ultLedMode": 0})
mock_smlight_client.actions.ambilight.reset_mock()
mock_ultima_client.actions.ambilight.reset_mock()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
@@ -302,21 +276,19 @@ async def test_light_turn_on_when_on_is_noop(
blocking=True,
)
mock_smlight_client.actions.ambilight.assert_not_called()
mock_ultima_client.actions.ambilight.assert_not_called()
async def test_light_state_handles_invalid_attributes_from_sse(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test state update gracefully handles invalid mode and invalid hex color."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "light.mock_title_ambilight"
fire_ambi = _build_fire_sse_ambilight(hass, mock_smlight_client)
fire_ambi = _build_fire_sse_ambilight(hass, mock_ultima_client)
await fire_ambi({"ultLedMode": None, "ultLedColor": "#GG0000"})
@@ -335,18 +307,16 @@ async def test_light_state_handles_invalid_attributes_from_sse(
async def test_ambilight_connection_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
mock_ultima_client: MagicMock,
) -> None:
"""Test connection error handling."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "light.mock_title_ambilight"
state = hass.states.get(entity_id)
assert state.state != STATE_UNAVAILABLE
mock_smlight_client.actions.ambilight.side_effect = SmlightConnectionError
mock_ultima_client.actions.ambilight.side_effect = SmlightConnectionError
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
@@ -356,10 +326,10 @@ async def test_ambilight_connection_error(
blocking=True,
)
mock_smlight_client.actions.ambilight.side_effect = None
mock_smlight_client.actions.ambilight.reset_mock()
mock_ultima_client.actions.ambilight.side_effect = None
mock_ultima_client.actions.ambilight.reset_mock()
fire_ambi = _build_fire_sse_ambilight(hass, mock_smlight_client)
fire_ambi = _build_fire_sse_ambilight(hass, mock_ultima_client)
await hass.services.async_call(
LIGHT_DOMAIN,
@@ -368,7 +338,7 @@ async def test_ambilight_connection_error(
blocking=True,
)
mock_smlight_client.actions.ambilight.assert_called_once_with(
mock_ultima_client.actions.ambilight.assert_called_once_with(
AmbilightPayload(ultLedMode=AmbiEffect.WSULT_SOLID)
)