From 3ea15f274341b8da42f4cb2389db5d7500f931be Mon Sep 17 00:00:00 2001 From: TimL Date: Thu, 9 Apr 2026 21:20:01 +1000 Subject: [PATCH] Refactor Ultima fixtures to reduce duplication (#167731) --- tests/components/smlight/conftest.py | 13 +++ tests/components/smlight/test_infrared.py | 38 ++------ tests/components/smlight/test_light.py | 106 ++++++++-------------- 3 files changed, 59 insertions(+), 98 deletions(-) diff --git a/tests/components/smlight/conftest.py b/tests/components/smlight/conftest.py index 982ccc3b7862..c25ff2abc066 100644 --- a/tests/components/smlight/conftest.py +++ b/tests/components/smlight/conftest.py @@ -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, diff --git a/tests/components/smlight/test_infrared.py b/tests/components/smlight/test_infrared.py index 14677d44cc1a..6c9a7c878067 100644 --- a/tests/components/smlight/test_infrared.py +++ b/tests/components/smlight/test_infrared.py @@ -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" diff --git a/tests/components/smlight/test_light.py b/tests/components/smlight/test_light.py index 7a5a9d8f130b..b19c291304b0 100644 --- a/tests/components/smlight/test_light.py +++ b/tests/components/smlight/test_light.py @@ -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) )