diff --git a/.coveragerc b/.coveragerc index c887fc385e6..b267f6967ef 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1014,7 +1014,6 @@ omit = homeassistant/components/watson_tts/tts.py homeassistant/components/waze_travel_time/sensor.py homeassistant/components/webostv/* - homeassistant/components/wemo/* homeassistant/components/whois/sensor.py homeassistant/components/wiffi/* homeassistant/components/wink/* diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9604d0579af..75f8f1cdece 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -932,6 +932,9 @@ pyvolumio==0.1.3 # homeassistant.components.html5 pywebpush==1.9.2 +# homeassistant.components.wemo +pywemo==0.5.3 + # homeassistant.components.wilight pywilight==0.0.65 diff --git a/tests/components/wemo/__init__.py b/tests/components/wemo/__init__.py new file mode 100644 index 00000000000..33bdcacd37d --- /dev/null +++ b/tests/components/wemo/__init__.py @@ -0,0 +1 @@ +"""Tests for the wemo component.""" diff --git a/tests/components/wemo/conftest.py b/tests/components/wemo/conftest.py new file mode 100644 index 00000000000..78262ccc59c --- /dev/null +++ b/tests/components/wemo/conftest.py @@ -0,0 +1,75 @@ +"""Fixtures for pywemo.""" +import pytest +import pywemo + +from homeassistant.components.wemo import CONF_DISCOVERY, CONF_STATIC +from homeassistant.components.wemo.const import DOMAIN +from homeassistant.setup import async_setup_component + +from tests.async_mock import create_autospec, patch + +MOCK_HOST = "127.0.0.1" +MOCK_PORT = 50000 +MOCK_NAME = "WemoDeviceName" +MOCK_SERIAL_NUMBER = "WemoSerialNumber" + + +@pytest.fixture(name="pywemo_model") +def pywemo_model_fixture(): + """Fixture containing a pywemo class name used by pywemo_device_fixture.""" + return "Insight" + + +@pytest.fixture(name="pywemo_registry") +def pywemo_registry_fixture(): + """Fixture for SubscriptionRegistry instances.""" + registry = create_autospec(pywemo.SubscriptionRegistry) + + registry.callbacks = {} + + def on_func(device, type_filter, callback): + registry.callbacks[device.name] = callback + + registry.on.side_effect = on_func + + with patch("pywemo.SubscriptionRegistry", return_value=registry): + yield registry + + +@pytest.fixture(name="pywemo_device") +def pywemo_device_fixture(pywemo_registry, pywemo_model): + """Fixture for WeMoDevice instances.""" + device = create_autospec(getattr(pywemo, pywemo_model)) + device.host = MOCK_HOST + device.port = MOCK_PORT + device.name = MOCK_NAME + device.serialnumber = MOCK_SERIAL_NUMBER + device.model_name = pywemo_model + + url = f"http://{MOCK_HOST}:{MOCK_PORT}/setup.xml" + with patch("pywemo.setup_url_for_address", return_value=url), patch( + "pywemo.discovery.device_from_description", return_value=device + ): + yield device + + +@pytest.fixture(name="wemo_entity") +async def async_wemo_entity_fixture(hass, pywemo_device): + """Fixture for a Wemo entity in hass.""" + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: { + CONF_DISCOVERY: False, + CONF_STATIC: [f"{MOCK_HOST}:{MOCK_PORT}"], + }, + }, + ) + await hass.async_block_till_done() + + entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_entries = list(entity_registry.entities.values()) + assert len(entity_entries) == 1 + + yield entity_entries[0] diff --git a/tests/components/wemo/test_binary_sensor.py b/tests/components/wemo/test_binary_sensor.py new file mode 100644 index 00000000000..f217d0b168c --- /dev/null +++ b/tests/components/wemo/test_binary_sensor.py @@ -0,0 +1,60 @@ +"""Tests for the Wemo binary_sensor entity.""" + +import pytest + +from homeassistant.components.homeassistant import ( + DOMAIN as HA_DOMAIN, + SERVICE_UPDATE_ENTITY, +) +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON +from homeassistant.setup import async_setup_component + + +@pytest.fixture +def pywemo_model(): + """Pywemo Motion models use the binary_sensor platform.""" + return "Motion" + + +async def test_binary_sensor_registry_state_callback( + hass, pywemo_registry, pywemo_device, wemo_entity +): + """Verify that the binary_sensor receives state updates from the registry.""" + # On state. + pywemo_device.get_state.return_value = 1 + pywemo_registry.callbacks[pywemo_device.name](pywemo_device, "", "") + await hass.async_block_till_done() + assert hass.states.get(wemo_entity.entity_id).state == STATE_ON + + # Off state. + pywemo_device.get_state.return_value = 0 + pywemo_registry.callbacks[pywemo_device.name](pywemo_device, "", "") + await hass.async_block_till_done() + assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF + + +async def test_binary_sensor_update_entity( + hass, pywemo_registry, pywemo_device, wemo_entity +): + """Verify that the binary_sensor performs state updates.""" + await async_setup_component(hass, HA_DOMAIN, {}) + + # On state. + pywemo_device.get_state.return_value = 1 + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + assert hass.states.get(wemo_entity.entity_id).state == STATE_ON + + # Off state. + pywemo_device.get_state.return_value = 0 + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF diff --git a/tests/components/wemo/test_fan.py b/tests/components/wemo/test_fan.py new file mode 100644 index 00000000000..ed49519c771 --- /dev/null +++ b/tests/components/wemo/test_fan.py @@ -0,0 +1,85 @@ +"""Tests for the Wemo fan entity.""" + +import pytest + +from homeassistant.components.homeassistant import ( + DOMAIN as HA_DOMAIN, + SERVICE_UPDATE_ENTITY, +) +from homeassistant.components.wemo import fan +from homeassistant.components.wemo.const import DOMAIN +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON +from homeassistant.setup import async_setup_component + + +@pytest.fixture +def pywemo_model(): + """Pywemo Humidifier models use the fan platform.""" + return "Humidifier" + + +async def test_fan_registry_state_callback( + hass, pywemo_registry, pywemo_device, wemo_entity +): + """Verify that the fan receives state updates from the registry.""" + # On state. + pywemo_device.get_state.return_value = 1 + pywemo_registry.callbacks[pywemo_device.name](pywemo_device, "", "") + await hass.async_block_till_done() + assert hass.states.get(wemo_entity.entity_id).state == STATE_ON + + # Off state. + pywemo_device.get_state.return_value = 0 + pywemo_registry.callbacks[pywemo_device.name](pywemo_device, "", "") + await hass.async_block_till_done() + assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF + + +async def test_fan_update_entity(hass, pywemo_registry, pywemo_device, wemo_entity): + """Verify that the fan performs state updates.""" + await async_setup_component(hass, HA_DOMAIN, {}) + + # On state. + pywemo_device.get_state.return_value = 1 + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + assert hass.states.get(wemo_entity.entity_id).state == STATE_ON + + # Off state. + pywemo_device.get_state.return_value = 0 + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF + + +async def test_fan_reset_filter_service(hass, pywemo_device, wemo_entity): + """Verify that SERVICE_RESET_FILTER_LIFE is registered and works.""" + assert await hass.services.async_call( + DOMAIN, + fan.SERVICE_RESET_FILTER_LIFE, + {fan.ATTR_ENTITY_ID: wemo_entity.entity_id}, + blocking=True, + ) + pywemo_device.reset_filter_life.assert_called_with() + + +async def test_fan_set_humidity_service(hass, pywemo_device, wemo_entity): + """Verify that SERVICE_SET_HUMIDITY is registered and works.""" + assert await hass.services.async_call( + DOMAIN, + fan.SERVICE_SET_HUMIDITY, + { + fan.ATTR_ENTITY_ID: wemo_entity.entity_id, + fan.ATTR_TARGET_HUMIDITY: "50", + }, + blocking=True, + ) + pywemo_device.set_humidity.assert_called_with(fan.WEMO_HUMIDITY_50) diff --git a/tests/components/wemo/test_light_bridge.py b/tests/components/wemo/test_light_bridge.py new file mode 100644 index 00000000000..d76c714ba5e --- /dev/null +++ b/tests/components/wemo/test_light_bridge.py @@ -0,0 +1,56 @@ +"""Tests for the Wemo light entity via the bridge.""" + +import pytest +import pywemo + +from homeassistant.components.homeassistant import ( + DOMAIN as HA_DOMAIN, + SERVICE_UPDATE_ENTITY, +) +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON +from homeassistant.setup import async_setup_component + +from tests.async_mock import PropertyMock, create_autospec + + +@pytest.fixture +def pywemo_model(): + """Pywemo Bridge models use the light platform (WemoLight class).""" + return "Bridge" + + +@pytest.fixture(name="pywemo_bridge_light") +def pywemo_bridge_light_fixture(pywemo_device): + """Fixture for Bridge.Light WeMoDevice instances.""" + light = create_autospec(pywemo.ouimeaux_device.bridge.Light) + light.uniqueID = pywemo_device.serialnumber + light.name = pywemo_device.name + pywemo_device.Lights = {pywemo_device.serialnumber: light} + return light + + +async def test_light_update_entity( + hass, pywemo_registry, pywemo_bridge_light, wemo_entity +): + """Verify that the light performs state updates.""" + await async_setup_component(hass, HA_DOMAIN, {}) + + # On state. + type(pywemo_bridge_light).state = PropertyMock(return_value={"onoff": 1}) + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + assert hass.states.get(wemo_entity.entity_id).state == STATE_ON + + # Off state. + type(pywemo_bridge_light).state = PropertyMock(return_value={"onoff": 0}) + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF diff --git a/tests/components/wemo/test_light_dimmer.py b/tests/components/wemo/test_light_dimmer.py new file mode 100644 index 00000000000..e94634c6d15 --- /dev/null +++ b/tests/components/wemo/test_light_dimmer.py @@ -0,0 +1,58 @@ +"""Tests for the Wemo standalone/non-bridge light entity.""" + +import pytest + +from homeassistant.components.homeassistant import ( + DOMAIN as HA_DOMAIN, + SERVICE_UPDATE_ENTITY, +) +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON +from homeassistant.setup import async_setup_component + + +@pytest.fixture +def pywemo_model(): + """Pywemo Dimmer models use the light platform (WemoDimmer class).""" + return "Dimmer" + + +async def test_light_registry_state_callback( + hass, pywemo_registry, pywemo_device, wemo_entity +): + """Verify that the light receives state updates from the registry.""" + # On state. + pywemo_device.get_state.return_value = 1 + pywemo_registry.callbacks[pywemo_device.name](pywemo_device, "", "") + await hass.async_block_till_done() + assert hass.states.get(wemo_entity.entity_id).state == STATE_ON + + # Off state. + pywemo_device.get_state.return_value = 0 + pywemo_registry.callbacks[pywemo_device.name](pywemo_device, "", "") + await hass.async_block_till_done() + assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF + + +async def test_light_update_entity(hass, pywemo_registry, pywemo_device, wemo_entity): + """Verify that the light performs state updates.""" + await async_setup_component(hass, HA_DOMAIN, {}) + + # On state. + pywemo_device.get_state.return_value = 1 + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + assert hass.states.get(wemo_entity.entity_id).state == STATE_ON + + # Off state. + pywemo_device.get_state.return_value = 0 + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF diff --git a/tests/components/wemo/test_switch.py b/tests/components/wemo/test_switch.py new file mode 100644 index 00000000000..1ae8e3f9455 --- /dev/null +++ b/tests/components/wemo/test_switch.py @@ -0,0 +1,58 @@ +"""Tests for the Wemo switch entity.""" + +import pytest + +from homeassistant.components.homeassistant import ( + DOMAIN as HA_DOMAIN, + SERVICE_UPDATE_ENTITY, +) +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON +from homeassistant.setup import async_setup_component + + +@pytest.fixture +def pywemo_model(): + """Pywemo LightSwitch models use the switch platform.""" + return "LightSwitch" + + +async def test_switch_registry_state_callback( + hass, pywemo_registry, pywemo_device, wemo_entity +): + """Verify that the switch receives state updates from the registry.""" + # On state. + pywemo_device.get_state.return_value = 1 + pywemo_registry.callbacks[pywemo_device.name](pywemo_device, "", "") + await hass.async_block_till_done() + assert hass.states.get(wemo_entity.entity_id).state == STATE_ON + + # Off state. + pywemo_device.get_state.return_value = 0 + pywemo_registry.callbacks[pywemo_device.name](pywemo_device, "", "") + await hass.async_block_till_done() + assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF + + +async def test_switch_update_entity(hass, pywemo_registry, pywemo_device, wemo_entity): + """Verify that the switch performs state updates.""" + await async_setup_component(hass, HA_DOMAIN, {}) + + # On state. + pywemo_device.get_state.return_value = 1 + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + assert hass.states.get(wemo_entity.entity_id).state == STATE_ON + + # Off state. + pywemo_device.get_state.return_value = 0 + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, + blocking=True, + ) + assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF