From d270f9515be39abf7c1989c90956a5c78ed384d2 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 11 Jan 2021 16:05:11 +0100 Subject: [PATCH] Add zwave_js init module tests (#45048) * Add entry setup and unload test * Test home assistant stop * Test on connect and on disconnect * Test client connect timeout * Test ready node added * Test non ready node added * Test existing node not ready * Test device registry state * Add common test tools module * Add existing ready node test * Include init module in coverage calculation * Clean docstrings --- .coveragerc | 1 - homeassistant/components/zwave_js/__init__.py | 3 +- tests/components/zwave_js/common.py | 2 + tests/components/zwave_js/conftest.py | 13 +- tests/components/zwave_js/test_init.py | 212 ++++++++++++++++++ tests/components/zwave_js/test_sensor.py | 2 +- 6 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 tests/components/zwave_js/common.py create mode 100644 tests/components/zwave_js/test_init.py diff --git a/.coveragerc b/.coveragerc index 9a8b062468f..7f05f4c064b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1092,7 +1092,6 @@ omit = homeassistant/components/zoneminder/* homeassistant/components/supla/* homeassistant/components/zwave/util.py - homeassistant/components/zwave_js/__init__.py homeassistant/components/zwave_js/discovery.py homeassistant/components/zwave_js/entity.py homeassistant/components/zwave_js/light.py diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 605235f61ba..b1502163f79 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -18,6 +18,7 @@ from .const import DATA_CLIENT, DATA_UNSUBSCRIBE, DOMAIN, PLATFORMS from .discovery import async_discover_values LOGGER = logging.getLogger(__name__) +CONNECT_TIMEOUT = 10 async def async_setup(hass: HomeAssistant, config: dict) -> bool: @@ -113,7 +114,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # connect and throw error if connection failed asyncio.create_task(client.connect()) try: - async with timeout(10): + async with timeout(CONNECT_TIMEOUT): await initialized.wait() except asyncio.TimeoutError as err: for unsub in unsubs: diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py new file mode 100644 index 00000000000..8d2a2195913 --- /dev/null +++ b/tests/components/zwave_js/common.py @@ -0,0 +1,2 @@ +"""Provide common test tools for Z-Wave JS.""" +AIR_TEMPERATURE_SENSOR = "sensor.multisensor_6_air_temperature" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 61e50d86a2d..8dd24aaa3ab 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -1,14 +1,24 @@ """Provide common Z-Wave JS fixtures.""" import json -from unittest.mock import patch +from unittest.mock import DEFAULT, patch import pytest from zwave_js_server.model.driver import Driver from zwave_js_server.model.node import Node +from homeassistant.helpers.device_registry import ( + async_get_registry as async_get_device_registry, +) + from tests.common import MockConfigEntry, load_fixture +@pytest.fixture(name="device_registry") +async def device_registry_fixture(hass): + """Return the device registry.""" + return await async_get_device_registry(hass) + + @pytest.fixture(name="controller_state", scope="session") def controller_state_fixture(): """Load the controller state fixture data.""" @@ -49,6 +59,7 @@ async def integration_fixture(hass, client): def initialize_client(async_on_initialized): """Init the client.""" hass.async_create_task(async_on_initialized()) + return DEFAULT client.register_on_initialized.side_effect = initialize_client diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py new file mode 100644 index 00000000000..0df05aba864 --- /dev/null +++ b/tests/components/zwave_js/test_init.py @@ -0,0 +1,212 @@ +"""Test the Z-Wave JS init module.""" +from copy import deepcopy +from unittest.mock import DEFAULT, patch + +import pytest +from zwave_js_server.model.node import Node + +from homeassistant.components.zwave_js.const import DOMAIN +from homeassistant.config_entries import ( + ENTRY_STATE_LOADED, + ENTRY_STATE_NOT_LOADED, + ENTRY_STATE_SETUP_RETRY, +) +from homeassistant.const import STATE_UNAVAILABLE + +from .common import AIR_TEMPERATURE_SENSOR + +from tests.common import MockConfigEntry + + +@pytest.fixture(name="connect_timeout") +def connect_timeout_fixture(): + """Mock the connect timeout.""" + with patch("homeassistant.components.zwave_js.CONNECT_TIMEOUT", new=0) as timeout: + yield timeout + + +async def test_entry_setup_unload(hass, client, integration): + """Test the integration set up and unload.""" + entry = integration + + assert client.connect.call_count == 1 + assert client.register_on_initialized.call_count == 1 + assert client.register_on_disconnect.call_count == 1 + assert client.register_on_connect.call_count == 1 + assert entry.state == ENTRY_STATE_LOADED + + await hass.config_entries.async_unload(entry.entry_id) + + assert client.disconnect.call_count == 1 + assert client.register_on_initialized.return_value.call_count == 1 + assert client.register_on_disconnect.return_value.call_count == 1 + assert client.register_on_connect.return_value.call_count == 1 + assert entry.state == ENTRY_STATE_NOT_LOADED + + +async def test_home_assistant_stop(hass, client, integration): + """Test we clean up on home assistant stop.""" + await hass.async_stop() + + assert client.disconnect.call_count == 1 + + +async def test_on_connect_disconnect(hass, client, multisensor_6, integration): + """Test we handle disconnect and reconnect.""" + on_connect = client.register_on_connect.call_args[0][0] + on_disconnect = client.register_on_disconnect.call_args[0][0] + state = hass.states.get(AIR_TEMPERATURE_SENSOR) + + assert state + assert state.state != STATE_UNAVAILABLE + + client.connected = False + + await on_disconnect() + await hass.async_block_till_done() + + state = hass.states.get(AIR_TEMPERATURE_SENSOR) + + assert state + assert state.state == STATE_UNAVAILABLE + + client.connected = True + + await on_connect() + await hass.async_block_till_done() + + state = hass.states.get(AIR_TEMPERATURE_SENSOR) + + assert state + assert state.state != STATE_UNAVAILABLE + + +async def test_initialized_timeout(hass, client, connect_timeout): + """Test we handle a timeout during client initialization.""" + entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + + +async def test_on_node_added_ready( + hass, multisensor_6_state, client, integration, device_registry +): + """Test we handle a ready node added event.""" + node = Node(client, multisensor_6_state) + event = {"node": node} + air_temperature_device_id = f"{client.driver.controller.home_id}-{node.node_id}" + + state = hass.states.get(AIR_TEMPERATURE_SENSOR) + + assert not state # entity and device not yet added + assert not device_registry.async_get_device( + identifiers={(DOMAIN, air_temperature_device_id)} + ) + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + state = hass.states.get(AIR_TEMPERATURE_SENSOR) + + assert state # entity and device added + assert state.state != STATE_UNAVAILABLE + assert device_registry.async_get_device( + identifiers={(DOMAIN, air_temperature_device_id)} + ) + + +async def test_on_node_added_not_ready( + hass, multisensor_6_state, client, integration, device_registry +): + """Test we handle a non ready node added event.""" + node_data = deepcopy(multisensor_6_state) # Copy to allow modification in tests. + node = Node(client, node_data) + node.data["ready"] = False + event = {"node": node} + air_temperature_device_id = f"{client.driver.controller.home_id}-{node.node_id}" + + state = hass.states.get(AIR_TEMPERATURE_SENSOR) + + assert not state # entity and device not yet added + assert not device_registry.async_get_device( + identifiers={(DOMAIN, air_temperature_device_id)} + ) + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + state = hass.states.get(AIR_TEMPERATURE_SENSOR) + + assert not state # entity not yet added but device added in registry + assert device_registry.async_get_device( + identifiers={(DOMAIN, air_temperature_device_id)} + ) + + node.data["ready"] = True + node.emit("ready", event) + await hass.async_block_till_done() + + state = hass.states.get(AIR_TEMPERATURE_SENSOR) + + assert state # entity added + assert state.state != STATE_UNAVAILABLE + + +async def test_existing_node_ready( + hass, client, multisensor_6, integration, device_registry +): + """Test we handle a ready node that exists during integration setup.""" + node = multisensor_6 + air_temperature_device_id = f"{client.driver.controller.home_id}-{node.node_id}" + + state = hass.states.get(AIR_TEMPERATURE_SENSOR) + + assert state # entity and device added + assert state.state != STATE_UNAVAILABLE + assert device_registry.async_get_device( + identifiers={(DOMAIN, air_temperature_device_id)} + ) + + +async def test_existing_node_not_ready(hass, client, multisensor_6, device_registry): + """Test we handle a non ready node that exists during integration setup.""" + node = multisensor_6 + node.data = deepcopy(node.data) # Copy to allow modification in tests. + node.data["ready"] = False + event = {"node": node} + air_temperature_device_id = f"{client.driver.controller.home_id}-{node.node_id}" + entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) + entry.add_to_hass(hass) + + def initialize_client(async_on_initialized): + """Init the client.""" + hass.async_create_task(async_on_initialized()) + return DEFAULT + + client.register_on_initialized.side_effect = initialize_client + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(AIR_TEMPERATURE_SENSOR) + + assert not state # entity and device not yet added + assert not device_registry.async_get_device( + identifiers={(DOMAIN, air_temperature_device_id)} + ) + + node.data["ready"] = True + node.emit("ready", event) + await hass.async_block_till_done() + + state = hass.states.get(AIR_TEMPERATURE_SENSOR) + + assert state # entity and device added + assert state.state != STATE_UNAVAILABLE + assert device_registry.async_get_device( + identifiers={(DOMAIN, air_temperature_device_id)} + ) diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index 79876a5b453..75ce016fb04 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -1,7 +1,7 @@ """Test the Z-Wave JS sensor platform.""" from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS -AIR_TEMPERATURE_SENSOR = "sensor.multisensor_6_air_temperature" +from .common import AIR_TEMPERATURE_SENSOR async def test_numeric_sensor(hass, multisensor_6, integration):