diff --git a/.coveragerc b/.coveragerc index 6a302011868..c2fc5d8ceda 100644 --- a/.coveragerc +++ b/.coveragerc @@ -589,7 +589,6 @@ omit = homeassistant/components/lcn/climate.py homeassistant/components/lcn/cover.py homeassistant/components/lcn/helpers.py - homeassistant/components/lcn/light.py homeassistant/components/lcn/scene.py homeassistant/components/lcn/sensor.py homeassistant/components/lcn/services.py diff --git a/tests/components/lcn/fixtures/config.json b/tests/components/lcn/fixtures/config.json index 0fc15a32f5e..e9278d8e2cd 100644 --- a/tests/components/lcn/fixtures/config.json +++ b/tests/components/lcn/fixtures/config.json @@ -20,6 +20,37 @@ "dim_mode": "steps200" } ], + "lights": [ + { + "name": "Light_Output1", + "address": "pchk.s0.m7", + "output": "output1", + "dimmable": true, + "transition": 5 + }, + { + "name": "Light_Output2", + "address": "pchk.s0.m7", + "output": "output2", + "dimmable": false, + "transition": 0 + }, + { + "name": "Light_Relay1", + "address": "s0.m7", + "output": "relay1" + }, + { + "name": "Light_Relay3", + "address": "myhome.s0.m7", + "output": "relay3" + }, + { + "name": "Light_Relay4", + "address": "myhome.s0.m7", + "output": "relay4" + } + ], "switches": [ { "name": "Switch_Output1", diff --git a/tests/components/lcn/fixtures/config_entry_myhome.json b/tests/components/lcn/fixtures/config_entry_myhome.json index 8ab59d0087d..a0f8e7d3e10 100644 --- a/tests/components/lcn/fixtures/config_entry_myhome.json +++ b/tests/components/lcn/fixtures/config_entry_myhome.json @@ -7,5 +7,28 @@ "sk_num_tries": 0, "dim_mode": "STEPS200", "devices": [], - "entities": [] + "entities": [ + { + "address": [0, 7, false], + "name": "Light_Relay3", + "resource": "relay3", + "domain": "light", + "domain_data": { + "output": "RELAY3", + "dimmable": false, + "transition": 0.0 + } + }, + { + "address": [0, 7, false], + "name": "Light_Relay4", + "resource": "relay4", + "domain": "light", + "domain_data": { + "output": "RELAY4", + "dimmable": false, + "transition": 0.0 + } + } + ] } diff --git a/tests/components/lcn/fixtures/config_entry_pchk.json b/tests/components/lcn/fixtures/config_entry_pchk.json index a92339cf9de..9d34add37ff 100644 --- a/tests/components/lcn/fixtures/config_entry_pchk.json +++ b/tests/components/lcn/fixtures/config_entry_pchk.json @@ -23,6 +23,39 @@ } ], "entities": [ + { + "address": [0, 7, false], + "name": "Light_Output1", + "resource": "output1", + "domain": "light", + "domain_data": { + "output": "OUTPUT1", + "dimmable": true, + "transition": 5000.0 + } + }, + { + "address": [0, 7, false], + "name": "Light_Output2", + "resource": "output2", + "domain": "light", + "domain_data": { + "output": "OUTPUT2", + "dimmable": false, + "transition": 0 + } + }, + { + "address": [0, 7, false], + "name": "Light_Relay1", + "resource": "relay1", + "domain": "light", + "domain_data": { + "output": "RELAY1", + "dimmable": false, + "transition": 0.0 + } + }, { "address": [0, 7, false], "name": "Switch_Output1", diff --git a/tests/components/lcn/test_light.py b/tests/components/lcn/test_light.py new file mode 100644 index 00000000000..4fd58aab743 --- /dev/null +++ b/tests/components/lcn/test_light.py @@ -0,0 +1,335 @@ +"""Test for the LCN light platform.""" +from unittest.mock import patch + +from pypck.inputs import ModStatusOutput, ModStatusRelays +from pypck.lcn_addr import LcnAddr +from pypck.lcn_defs import RelayStateModifier + +from homeassistant.components.lcn.helpers import get_device_connection +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_TRANSITION, + DOMAIN as DOMAIN_LIGHT, + SUPPORT_BRIGHTNESS, + SUPPORT_TRANSITION, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) + +from .conftest import MockModuleConnection + + +async def test_setup_lcn_light(hass, lcn_connection): + """Test the setup of light.""" + for entity_id in ( + "light.light_output1", + "light.light_output2", + "light.light_relay1", + ): + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_OFF + + +async def test_entity_state(hass, lcn_connection): + """Test state of entity.""" + state = hass.states.get("light.light_output1") + assert state + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] + == SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + ) + + state = hass.states.get("light.light_output2") + assert state + assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_TRANSITION + + +async def test_entity_attributes(hass, entry, lcn_connection): + """Test the attributes of an entity.""" + entity_registry = await hass.helpers.entity_registry.async_get_registry() + + entity_output = entity_registry.async_get("light.light_output1") + + assert entity_output + assert entity_output.unique_id == f"{entry.entry_id}-m000007-output1" + assert entity_output.original_name == "Light_Output1" + + entity_relay = entity_registry.async_get("light.light_relay1") + + assert entity_relay + assert entity_relay.unique_id == f"{entry.entry_id}-m000007-relay1" + assert entity_relay.original_name == "Light_Relay1" + + +@patch.object(MockModuleConnection, "dim_output") +async def test_output_turn_on(dim_output, hass, lcn_connection): + """Test the output light turns on.""" + # command failed + dim_output.return_value = False + + await hass.services.async_call( + DOMAIN_LIGHT, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.light_output1"}, + blocking=True, + ) + await hass.async_block_till_done() + dim_output.assert_awaited_with(0, 100, 9) + + state = hass.states.get("light.light_output1") + assert state is not None + assert state.state != STATE_ON + + # command success + dim_output.reset_mock(return_value=True) + dim_output.return_value = True + + await hass.services.async_call( + DOMAIN_LIGHT, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.light_output1"}, + blocking=True, + ) + await hass.async_block_till_done() + dim_output.assert_awaited_with(0, 100, 9) + + state = hass.states.get("light.light_output1") + assert state is not None + assert state.state == STATE_ON + + +@patch.object(MockModuleConnection, "dim_output") +async def test_output_turn_on_with_attributes(dim_output, hass, lcn_connection): + """Test the output light turns on.""" + dim_output.return_value = True + + await hass.services.async_call( + DOMAIN_LIGHT, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: "light.light_output1", + ATTR_BRIGHTNESS: 50, + ATTR_TRANSITION: 2, + }, + blocking=True, + ) + await hass.async_block_till_done() + dim_output.assert_awaited_with(0, 19, 6) + + state = hass.states.get("light.light_output1") + assert state is not None + assert state.state == STATE_ON + + +@patch.object(MockModuleConnection, "dim_output") +async def test_output_turn_off(dim_output, hass, lcn_connection): + """Test the output light turns off.""" + state = hass.states.get("light.light_output1") + state.state = STATE_ON + + # command failed + dim_output.return_value = False + + await hass.services.async_call( + DOMAIN_LIGHT, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.light_output1"}, + blocking=True, + ) + await hass.async_block_till_done() + dim_output.assert_awaited_with(0, 0, 9) + + state = hass.states.get("light.light_output1") + assert state is not None + assert state.state != STATE_OFF + + # command success + dim_output.reset_mock(return_value=True) + dim_output.return_value = True + + await hass.services.async_call( + DOMAIN_LIGHT, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.light_output1"}, + blocking=True, + ) + await hass.async_block_till_done() + dim_output.assert_awaited_with(0, 0, 9) + + state = hass.states.get("light.light_output1") + assert state is not None + assert state.state == STATE_OFF + + +@patch.object(MockModuleConnection, "dim_output") +async def test_output_turn_off_with_attributes(dim_output, hass, lcn_connection): + """Test the output light turns off.""" + dim_output.return_value = True + + state = hass.states.get("light.light_output1") + state.state = STATE_ON + + await hass.services.async_call( + DOMAIN_LIGHT, + SERVICE_TURN_OFF, + { + ATTR_ENTITY_ID: "light.light_output1", + ATTR_TRANSITION: 2, + }, + blocking=True, + ) + await hass.async_block_till_done() + dim_output.assert_awaited_with(0, 0, 6) + + state = hass.states.get("light.light_output1") + assert state is not None + assert state.state == STATE_OFF + + +@patch.object(MockModuleConnection, "control_relays") +async def test_relay_turn_on(control_relays, hass, lcn_connection): + """Test the relay light turns on.""" + states = [RelayStateModifier.NOCHANGE] * 8 + states[0] = RelayStateModifier.ON + + # command failed + control_relays.return_value = False + + await hass.services.async_call( + DOMAIN_LIGHT, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.light_relay1"}, + blocking=True, + ) + await hass.async_block_till_done() + control_relays.assert_awaited_with(states) + + state = hass.states.get("light.light_relay1") + assert state is not None + assert state.state != STATE_ON + + # command success + control_relays.reset_mock(return_value=True) + control_relays.return_value = True + + await hass.services.async_call( + DOMAIN_LIGHT, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.light_relay1"}, + blocking=True, + ) + await hass.async_block_till_done() + control_relays.assert_awaited_with(states) + + state = hass.states.get("light.light_relay1") + assert state is not None + assert state.state == STATE_ON + + +@patch.object(MockModuleConnection, "control_relays") +async def test_relay_turn_off(control_relays, hass, lcn_connection): + """Test the relay light turns off.""" + states = [RelayStateModifier.NOCHANGE] * 8 + states[0] = RelayStateModifier.OFF + + state = hass.states.get("light.light_relay1") + state.state = STATE_ON + + # command failed + control_relays.return_value = False + + await hass.services.async_call( + DOMAIN_LIGHT, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.light_relay1"}, + blocking=True, + ) + await hass.async_block_till_done() + control_relays.assert_awaited_with(states) + + state = hass.states.get("light.light_relay1") + assert state is not None + assert state.state != STATE_OFF + + # command success + control_relays.reset_mock(return_value=True) + control_relays.return_value = True + + await hass.services.async_call( + DOMAIN_LIGHT, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.light_relay1"}, + blocking=True, + ) + await hass.async_block_till_done() + control_relays.assert_awaited_with(states) + + state = hass.states.get("light.light_relay1") + assert state is not None + assert state.state == STATE_OFF + + +async def test_pushed_output_status_change(hass, entry, lcn_connection): + """Test the output light changes its state on status received.""" + device_connection = get_device_connection(hass, (0, 7, False), entry) + address = LcnAddr(0, 7, False) + + # push status "on" + inp = ModStatusOutput(address, 0, 50) + await device_connection.async_process_input(inp) + await hass.async_block_till_done() + + state = hass.states.get("light.light_output1") + assert state is not None + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 127 + + # push status "off" + inp = ModStatusOutput(address, 0, 0) + await device_connection.async_process_input(inp) + await hass.async_block_till_done() + + state = hass.states.get("light.light_output1") + assert state is not None + assert state.state == STATE_OFF + + +async def test_pushed_relay_status_change(hass, entry, lcn_connection): + """Test the relay light changes its state on status received.""" + device_connection = get_device_connection(hass, (0, 7, False), entry) + address = LcnAddr(0, 7, False) + states = [False] * 8 + + # push status "on" + states[0] = True + inp = ModStatusRelays(address, states) + await device_connection.async_process_input(inp) + await hass.async_block_till_done() + + state = hass.states.get("light.light_relay1") + assert state is not None + assert state.state == STATE_ON + + # push status "off" + states[0] = False + inp = ModStatusRelays(address, states) + await device_connection.async_process_input(inp) + await hass.async_block_till_done() + + state = hass.states.get("light.light_relay1") + assert state is not None + assert state.state == STATE_OFF + + +async def test_unload_config_entry(hass, entry, lcn_connection): + """Test the light is removed when the config entry is unloaded.""" + await hass.config_entries.async_unload(entry.entry_id) + assert hass.states.get("light.light_output1").state == STATE_UNAVAILABLE