async def test_send_command_schema(client_session, url, ws_client, driver_ready, driver): """Test sending unsupported command.""" client = Client(url, client_session) await client.connect() assert client.connected client.driver = driver await client.listen(driver_ready) ws_client.receive.assert_awaited() # test schema version is at server maximum if client.version.max_schema_version < MAX_SERVER_SCHEMA_VERSION: assert client.schema_version == client.version.max_schema_version # send command of current schema version should not fail with pytest.raises(NotConnected): await client.async_send_command({"command": "test"}, require_schema=client.schema_version) # send command of unsupported schema version should fail with pytest.raises(InvalidServerVersion): await client.async_send_command({"command": "test"}, require_schema=client.schema_version + 2) with pytest.raises(InvalidServerVersion): await client.async_send_command_no_wait( {"command": "test"}, require_schema=client.schema_version + 2)
async def client_fixture(loop, client_session, ws_client, uuid4): """Return a client with a mock websocket transport. This fixture needs to be a coroutine function to get an event loop when creating the client. """ client = Client("ws://test.org", client_session) client._client = ws_client return client
async def client_fixture(loop, client_session, ws_client, uuid4): """Return a client with a mock websocket transport. This fixture needs to be a coroutine function to get an event loop when creating the client. """ client_session.ws_connect.side_effect = AsyncMock(return_value=ws_client) client = Client("ws://test.org", client_session) client.state = STATE_CONNECTED client.client = ws_client return client
async def test_listen_unknown_result_type(client_session, url, ws_client, result, driver_ready, driver): """Test websocket message with unknown type on listen.""" client = Client(url, client_session) await client.connect() assert client.connected # Make sure there's a driver so we can test an unknown event. client.driver = driver result["type"] = "unknown" # Receiving an unknown message type should not error. await client.listen(driver_ready) ws_client.receive.assert_awaited()
async def test_listen_event(client_session, url, ws_client, messages, ws_message, result, driver_ready): """Test receiving event result type on listen.""" client = Client(url, client_session) await client.connect() assert client.connected result["type"] = "event" result["event"] = { "source": "node", "event": "value updated", "nodeId": 52, "args": { "commandClassName": "Basic", "commandClass": 32, "endpoint": 0, "property": "currentValue", "newValue": 255, "prevValue": 255, "propertyName": "currentValue", }, } messages.append(ws_message) await client.listen(driver_ready) ws_client.receive.assert_awaited()
async def test_listen_without_connect(client_session, url, driver_ready): """Test listen without first being connected.""" client = Client(url, client_session) assert not client.connected with pytest.raises(InvalidState): await client.listen(driver_ready)
async def test_listen(client_session, url, driver_ready): """Test client listen.""" client = Client(url, client_session) assert not client.driver await client.connect() assert client.connected asyncio.create_task(client.listen(driver_ready)) await driver_ready.wait() assert client.driver await client.disconnect() assert not client.connected
async def test_send_json_when_disconnected(client_session, url): """Test send json message when disconnected.""" client = Client(url, client_session) assert not client.connected with pytest.raises(NotConnected): await client.async_send_command({"test": None})
async def test_max_schema_version(client_session, url, version_data): """Test client connect with invalid schema version.""" version_data["maxSchemaVersion"] = 0 client = Client(url, client_session) with pytest.raises(InvalidServerVersion): await client.connect() assert not client.connected
async def test_cannot_connect(client_session, url, error): """Test cannot connect.""" client_session.ws_connect.side_effect = error client = Client(url, client_session) with pytest.raises(CannotConnect): await client.connect() assert not client.connected
async def test_listen_not_success(client_session, url, result, driver_ready): """Test receive result message with success False on listen.""" result["success"] = False result["errorCode"] = "error_code" client = Client(url, client_session) await client.connect() with pytest.raises(FailedCommand): await client.listen(driver_ready) assert not client.connected
async def test_listen_invalid_message_data(client_session, url, messages, ws_message, driver_ready): """Test websocket message data that should raise on listen.""" client = Client(url, client_session) await client.connect() assert client.connected ws_message.json.side_effect = ValueError("Boom") messages.append(ws_message) with pytest.raises(InvalidMessage): await client.listen(driver_ready)
async def test_get_log_config_not_success( client_session, url, get_log_config_data, driver_ready ): """Test receive log config message with success False on listen.""" get_log_config_data["success"] = False get_log_config_data["errorCode"] = "error_code" client = Client(url, client_session) await client.connect() with pytest.raises(FailedCommand): await client.listen(driver_ready) assert not client.connected
async def test_listen_error_message_types(client_session, url, messages, ws_message, message_type, exception, driver_ready): """Test different websocket message types that should raise on listen.""" client = Client(url, client_session) await client.connect() assert client.connected ws_message.type = message_type messages.append(ws_message) with pytest.raises(exception): await client.listen(driver_ready)
async def test_listen_disconnect_message_types(client_session, url, ws_client, messages, ws_message, message_type, driver_ready): """Test different websocket message types that stop listen.""" async with Client(url, client_session) as client: assert client.connected ws_message.type = message_type messages.append(ws_message) # This should break out of the listen loop before handling the received message. # Otherwise there will be an error. await client.listen(driver_ready) # Assert that we received a message. ws_client.receive.assert_awaited()
async def test_listen_client_error(client_session, url, ws_client, messages, ws_message, driver_ready): """Test websocket error on listen.""" client = Client(url, client_session) await client.connect() assert client.connected messages.append(ws_message) ws_client.receive.side_effect = asyncio.CancelledError() # This should break out of the listen loop before any message is received. with pytest.raises(asyncio.CancelledError): await client.listen(driver_ready) assert not ws_message.json.called
async def test_connect_disconnect(client_session, url): """Test client connect and disconnect.""" async with Client(url, client_session) as client: assert client.connected assert not client.connected
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Z-Wave JS from a config entry.""" client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass)) initialized = asyncio.Event() dev_reg = await device_registry.async_get_registry(hass) async def async_on_connect() -> None: """Handle websocket is (re)connected.""" LOGGER.info("Connected to Zwave JS Server") if initialized.is_set(): # update entity availability async_dispatcher_send(hass, f"{DOMAIN}_connection_state") async def async_on_disconnect() -> None: """Handle websocket is disconnected.""" LOGGER.info("Disconnected from Zwave JS Server") async_dispatcher_send(hass, f"{DOMAIN}_connection_state") async def async_on_initialized() -> None: """Handle initial full state received.""" LOGGER.info("Connection to Zwave JS Server initialized.") initialized.set() @callback def async_on_node_ready(node: ZwaveNode) -> None: """Handle node ready event.""" LOGGER.debug("Processing node %s", node) # register (or update) node in device registry register_node_in_dev_reg(hass, entry, dev_reg, client, node) # run discovery on all node values and create/update entities for disc_info in async_discover_values(node): LOGGER.debug("Discovered entity: %s", disc_info) async_dispatcher_send(hass, f"{DOMAIN}_add_{disc_info.platform}", disc_info) @callback def async_on_node_added(node: ZwaveNode) -> None: """Handle node added event.""" # we only want to run discovery when the node has reached ready state, # otherwise we'll have all kinds of missing info issues. if node.ready: async_on_node_ready(node) return # if node is not yet ready, register one-time callback for ready state LOGGER.debug("Node added: %s - waiting for it to become ready.", node.node_id) node.once( "ready", lambda event: async_on_node_ready(event["node"]), ) # we do submit the node to device registry so user has # some visual feedback that something is (in the process of) being added register_node_in_dev_reg(hass, entry, dev_reg, client, node) async def handle_ha_shutdown(event: Event) -> None: """Handle HA shutdown.""" await client.disconnect() # register main event callbacks. unsubs = [ client.register_on_initialized(async_on_initialized), client.register_on_disconnect(async_on_disconnect), client.register_on_connect(async_on_connect), hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown), ] # connect and throw error if connection failed asyncio.create_task(client.connect()) try: async with timeout(CONNECT_TIMEOUT): await initialized.wait() except asyncio.TimeoutError as err: for unsub in unsubs: unsub() await client.disconnect() raise ConfigEntryNotReady from err hass.data[DOMAIN][entry.entry_id] = { DATA_CLIENT: client, DATA_UNSUBSCRIBE: unsubs, } # Set up websocket API async_register_api(hass) async def start_platforms() -> None: """Start platforms and perform discovery.""" # wait until all required platforms are ready await asyncio.gather( *[ hass.config_entries.async_forward_entry_setup(entry, component) for component in PLATFORMS ] ) # run discovery on all ready nodes for node in client.driver.controller.nodes.values(): async_on_node_added(node) # listen for new nodes being added to the mesh client.driver.controller.on( "node added", lambda event: async_on_node_added(event["node"]) ) hass.async_create_task(start_platforms()) return True