"""Test the Insteon properties APIs.""" import json from unittest.mock import patch import pytest from homeassistant.components import insteon from homeassistant.components.insteon.api import async_load_api from homeassistant.components.insteon.api.device import INSTEON_DEVICE_NOT_FOUND from homeassistant.components.insteon.api.properties import ( DEVICE_ADDRESS, ID, NON_TOGGLE_MASK, NON_TOGGLE_OFF_MODE, NON_TOGGLE_ON_MODE, NON_TOGGLE_ON_OFF_MASK, PROPERTY_NAME, PROPERTY_VALUE, RADIO_BUTTON_GROUP_PROP, TOGGLE_MODES, TOGGLE_ON_OFF_MODE, TOGGLE_PROP, TYPE, _get_radio_button_properties, _get_toggle_properties, ) from .mock_devices import MockDevices from tests.common import load_fixture @pytest.fixture(name="properties_data", scope="session") def aldb_data_fixture(): """Load the controller state fixture data.""" return json.loads(load_fixture("insteon/kpl_properties.json")) async def _setup(hass, hass_ws_client, properties_data): """Set up tests.""" ws_client = await hass_ws_client(hass) devices = MockDevices() await devices.async_load() devices.fill_properties("33.33.33", properties_data) async_load_api(hass) return ws_client, devices async def test_get_properties(hass, hass_ws_client, properties_data): """Test getting an Insteon device's properties.""" ws_client, devices = await _setup(hass, hass_ws_client, properties_data) with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( {ID: 2, TYPE: "insteon/properties/get", DEVICE_ADDRESS: "33.33.33"} ) msg = await ws_client.receive_json() assert msg["success"] assert len(msg["result"]["properties"]) == 54 async def test_change_operating_flag(hass, hass_ws_client, properties_data): """Test changing an Insteon device's properties.""" ws_client, devices = await _setup(hass, hass_ws_client, properties_data) with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( { ID: 2, TYPE: "insteon/properties/change", DEVICE_ADDRESS: "33.33.33", PROPERTY_NAME: "led_off", PROPERTY_VALUE: True, } ) msg = await ws_client.receive_json() assert msg["success"] assert devices["33.33.33"].operating_flags["led_off"].is_dirty async def test_change_property(hass, hass_ws_client, properties_data): """Test changing an Insteon device's properties.""" ws_client, devices = await _setup(hass, hass_ws_client, properties_data) with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( { ID: 2, TYPE: "insteon/properties/change", DEVICE_ADDRESS: "33.33.33", PROPERTY_NAME: "on_mask", PROPERTY_VALUE: 100, } ) msg = await ws_client.receive_json() assert msg["success"] assert devices["33.33.33"].properties["on_mask"].new_value == 100 assert devices["33.33.33"].properties["on_mask"].is_dirty async def test_change_ramp_rate_property(hass, hass_ws_client, properties_data): """Test changing an Insteon device's properties.""" ws_client, devices = await _setup(hass, hass_ws_client, properties_data) with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( { ID: 2, TYPE: "insteon/properties/change", DEVICE_ADDRESS: "33.33.33", PROPERTY_NAME: "ramp_rate", PROPERTY_VALUE: 4.5, } ) msg = await ws_client.receive_json() assert msg["success"] assert devices["33.33.33"].properties["ramp_rate"].new_value == 0x1A assert devices["33.33.33"].properties["ramp_rate"].is_dirty async def test_change_radio_button_group(hass, hass_ws_client, properties_data): """Test changing an Insteon device's properties.""" ws_client, devices = await _setup(hass, hass_ws_client, properties_data) rb_props, schema = _get_radio_button_properties(devices["33.33.33"]) # Make sure the baseline is correct assert rb_props[0]["name"] == f"{RADIO_BUTTON_GROUP_PROP}0" assert rb_props[0]["value"] == [4, 5] assert rb_props[1]["value"] == [7, 8] assert rb_props[2]["value"] == [] assert schema[f"{RADIO_BUTTON_GROUP_PROP}0"]["options"].get(1) assert schema[f"{RADIO_BUTTON_GROUP_PROP}1"]["options"].get(1) assert devices["33.33.33"].properties["on_mask"].value == 0 assert devices["33.33.33"].properties["off_mask"].value == 0 assert not devices["33.33.33"].properties["on_mask"].is_dirty assert not devices["33.33.33"].properties["off_mask"].is_dirty # Add button 1 to the group rb_props[0]["value"].append(1) with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( { ID: 2, TYPE: "insteon/properties/change", DEVICE_ADDRESS: "33.33.33", PROPERTY_NAME: f"{RADIO_BUTTON_GROUP_PROP}0", PROPERTY_VALUE: rb_props[0]["value"], } ) msg = await ws_client.receive_json() assert msg["success"] new_rb_props, _ = _get_radio_button_properties(devices["33.33.33"]) assert 1 in new_rb_props[0]["value"] assert 4 in new_rb_props[0]["value"] assert 5 in new_rb_props[0]["value"] assert schema[f"{RADIO_BUTTON_GROUP_PROP}0"]["options"].get(1) assert schema[f"{RADIO_BUTTON_GROUP_PROP}1"]["options"].get(1) assert devices["33.33.33"].properties["on_mask"].new_value == 0x18 assert devices["33.33.33"].properties["off_mask"].new_value == 0x18 assert devices["33.33.33"].properties["on_mask"].is_dirty assert devices["33.33.33"].properties["off_mask"].is_dirty # Remove button 5 rb_props[0]["value"].remove(5) await ws_client.send_json( { ID: 3, TYPE: "insteon/properties/change", DEVICE_ADDRESS: "33.33.33", PROPERTY_NAME: f"{RADIO_BUTTON_GROUP_PROP}0", PROPERTY_VALUE: rb_props[0]["value"], } ) msg = await ws_client.receive_json() assert msg["success"] new_rb_props, _ = _get_radio_button_properties(devices["33.33.33"]) assert 1 in new_rb_props[0]["value"] assert 4 in new_rb_props[0]["value"] assert 5 not in new_rb_props[0]["value"] assert schema[f"{RADIO_BUTTON_GROUP_PROP}0"]["options"].get(1) assert schema[f"{RADIO_BUTTON_GROUP_PROP}1"]["options"].get(1) assert devices["33.33.33"].properties["on_mask"].new_value == 0x08 assert devices["33.33.33"].properties["off_mask"].new_value == 0x08 assert devices["33.33.33"].properties["on_mask"].is_dirty assert devices["33.33.33"].properties["off_mask"].is_dirty # Remove button group 1 rb_props[1]["value"] = [] await ws_client.send_json( { ID: 5, TYPE: "insteon/properties/change", DEVICE_ADDRESS: "33.33.33", PROPERTY_NAME: f"{RADIO_BUTTON_GROUP_PROP}1", PROPERTY_VALUE: rb_props[1]["value"], } ) msg = await ws_client.receive_json() assert msg["success"] new_rb_props, _ = _get_radio_button_properties(devices["33.33.33"]) assert len(new_rb_props) == 2 assert new_rb_props[0]["value"] == [1, 4] assert new_rb_props[1]["value"] == [] async def test_create_radio_button_group(hass, hass_ws_client, properties_data): """Test changing an Insteon device's properties.""" ws_client, devices = await _setup(hass, hass_ws_client, properties_data) rb_props, _ = _get_radio_button_properties(devices["33.33.33"]) # Make sure the baseline is correct assert len(rb_props) == 3 print(rb_props) rb_props[0]["value"].append("1") with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( { ID: 2, TYPE: "insteon/properties/change", DEVICE_ADDRESS: "33.33.33", PROPERTY_NAME: f"{RADIO_BUTTON_GROUP_PROP}2", PROPERTY_VALUE: ["1", "3"], } ) msg = await ws_client.receive_json() assert msg["success"] new_rb_props, new_schema = _get_radio_button_properties(devices["33.33.33"]) assert len(new_rb_props) == 4 assert 1 in new_rb_props[0]["value"] assert new_schema[f"{RADIO_BUTTON_GROUP_PROP}0"]["options"].get(1) assert not new_schema[f"{RADIO_BUTTON_GROUP_PROP}1"]["options"].get(1) assert devices["33.33.33"].properties["on_mask"].new_value == 4 assert devices["33.33.33"].properties["off_mask"].new_value == 4 assert devices["33.33.33"].properties["on_mask"].is_dirty assert devices["33.33.33"].properties["off_mask"].is_dirty async def test_change_toggle_property(hass, hass_ws_client, properties_data): """Update a button's toggle mode.""" ws_client, devices = await _setup(hass, hass_ws_client, properties_data) device = devices["33.33.33"] toggle_props, _ = _get_toggle_properties(devices["33.33.33"]) # Make sure the baseline is correct assert toggle_props[0]["name"] == f"{TOGGLE_PROP}{device.groups[1].name}" assert toggle_props[0]["value"] == TOGGLE_MODES[TOGGLE_ON_OFF_MODE] assert toggle_props[1]["value"] == TOGGLE_MODES[NON_TOGGLE_ON_MODE] assert device.properties[NON_TOGGLE_MASK].value == 2 assert device.properties[NON_TOGGLE_ON_OFF_MASK].value == 2 assert not device.properties[NON_TOGGLE_MASK].is_dirty assert not device.properties[NON_TOGGLE_ON_OFF_MASK].is_dirty with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( { ID: 2, TYPE: "insteon/properties/change", DEVICE_ADDRESS: "33.33.33", PROPERTY_NAME: toggle_props[0]["name"], PROPERTY_VALUE: 1, } ) msg = await ws_client.receive_json() assert msg["success"] new_toggle_props, _ = _get_toggle_properties(devices["33.33.33"]) assert new_toggle_props[0]["value"] == TOGGLE_MODES[NON_TOGGLE_ON_MODE] assert device.properties[NON_TOGGLE_MASK].new_value == 3 assert device.properties[NON_TOGGLE_ON_OFF_MASK].new_value == 3 assert device.properties[NON_TOGGLE_MASK].is_dirty assert device.properties[NON_TOGGLE_ON_OFF_MASK].is_dirty await ws_client.send_json( { ID: 3, TYPE: "insteon/properties/change", DEVICE_ADDRESS: "33.33.33", PROPERTY_NAME: toggle_props[0]["name"], PROPERTY_VALUE: 2, } ) msg = await ws_client.receive_json() assert msg["success"] new_toggle_props, _ = _get_toggle_properties(devices["33.33.33"]) assert new_toggle_props[0]["value"] == TOGGLE_MODES[NON_TOGGLE_OFF_MODE] assert device.properties[NON_TOGGLE_MASK].new_value == 3 assert device.properties[NON_TOGGLE_ON_OFF_MASK].new_value is None assert device.properties[NON_TOGGLE_MASK].is_dirty assert not device.properties[NON_TOGGLE_ON_OFF_MASK].is_dirty await ws_client.send_json( { ID: 4, TYPE: "insteon/properties/change", DEVICE_ADDRESS: "33.33.33", PROPERTY_NAME: toggle_props[1]["name"], PROPERTY_VALUE: 0, } ) msg = await ws_client.receive_json() assert msg["success"] new_toggle_props, _ = _get_toggle_properties(devices["33.33.33"]) assert new_toggle_props[1]["value"] == TOGGLE_MODES[TOGGLE_ON_OFF_MODE] assert device.properties[NON_TOGGLE_MASK].new_value == 1 assert device.properties[NON_TOGGLE_ON_OFF_MASK].new_value == 0 assert device.properties[NON_TOGGLE_MASK].is_dirty assert device.properties[NON_TOGGLE_ON_OFF_MASK].is_dirty async def test_write_properties(hass, hass_ws_client, properties_data): """Test getting an Insteon device's properties.""" ws_client, devices = await _setup(hass, hass_ws_client, properties_data) with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( {ID: 2, TYPE: "insteon/properties/write", DEVICE_ADDRESS: "33.33.33"} ) msg = await ws_client.receive_json() assert msg["success"] assert devices["33.33.33"].async_write_op_flags.call_count == 1 assert devices["33.33.33"].async_write_ext_properties.call_count == 1 async def test_write_properties_failure(hass, hass_ws_client, properties_data): """Test getting an Insteon device's properties.""" ws_client, devices = await _setup(hass, hass_ws_client, properties_data) with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( {ID: 2, TYPE: "insteon/properties/write", DEVICE_ADDRESS: "22.22.22"} ) msg = await ws_client.receive_json() assert not msg["success"] assert msg["error"]["code"] == "write_failed" async def test_load_properties(hass, hass_ws_client, properties_data): """Test getting an Insteon device's properties.""" ws_client, devices = await _setup(hass, hass_ws_client, properties_data) with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( {ID: 2, TYPE: "insteon/properties/load", DEVICE_ADDRESS: "33.33.33"} ) msg = await ws_client.receive_json() assert msg["success"] assert devices["33.33.33"].async_read_op_flags.call_count == 1 assert devices["33.33.33"].async_read_ext_properties.call_count == 1 async def test_load_properties_failure(hass, hass_ws_client, properties_data): """Test getting an Insteon device's properties.""" ws_client, devices = await _setup(hass, hass_ws_client, properties_data) with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( {ID: 2, TYPE: "insteon/properties/load", DEVICE_ADDRESS: "22.22.22"} ) msg = await ws_client.receive_json() assert not msg["success"] assert msg["error"]["code"] == "load_failed" async def test_reset_properties(hass, hass_ws_client, properties_data): """Test getting an Insteon device's properties.""" ws_client, devices = await _setup(hass, hass_ws_client, properties_data) device = devices["33.33.33"] device.operating_flags["led_off"].new_value = True device.properties["on_mask"].new_value = 100 assert device.operating_flags["led_off"].is_dirty assert device.properties["on_mask"].is_dirty with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( {ID: 2, TYPE: "insteon/properties/reset", DEVICE_ADDRESS: "33.33.33"} ) msg = await ws_client.receive_json() assert msg["success"] assert not device.operating_flags["led_off"].is_dirty assert not device.properties["on_mask"].is_dirty async def test_bad_address(hass, hass_ws_client, properties_data): """Test for a bad Insteon address.""" ws_client, _ = await _setup(hass, hass_ws_client, properties_data) ws_id = 0 for call in ["get", "write", "load", "reset"]: ws_id += 1 await ws_client.send_json( { ID: ws_id, TYPE: f"insteon/properties/{call}", DEVICE_ADDRESS: "99.99.99", } ) msg = await ws_client.receive_json() assert not msg["success"] assert msg["error"]["message"] == INSTEON_DEVICE_NOT_FOUND ws_id += 1 await ws_client.send_json( { ID: ws_id, TYPE: "insteon/properties/change", DEVICE_ADDRESS: "99.99.99", PROPERTY_NAME: "led_off", PROPERTY_VALUE: True, } ) msg = await ws_client.receive_json() assert not msg["success"] assert msg["error"]["message"] == INSTEON_DEVICE_NOT_FOUND