diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index ff0459ddbdd..88f8f25c8e2 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -395,6 +395,7 @@ def async_register_api(hass: HomeAssistant) -> None: websocket_api.async_register_command(hass, websocket_node_metadata) websocket_api.async_register_command(hass, websocket_node_alerts) websocket_api.async_register_command(hass, websocket_add_node) + websocket_api.async_register_command(hass, websocket_cancel_secure_bootstrap_s2) websocket_api.async_register_command(hass, websocket_grant_security_classes) websocket_api.async_register_command(hass, websocket_validate_dsk_and_enter_pin) websocket_api.async_register_command(hass, websocket_provision_smart_start_node) @@ -839,6 +840,29 @@ async def websocket_add_node( ) +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required(TYPE): "zwave_js/cancel_secure_bootstrap_s2", + vol.Required(ENTRY_ID): str, + } +) +@websocket_api.async_response +@async_handle_failed_command +@async_get_entry +async def websocket_cancel_secure_bootstrap_s2( + hass: HomeAssistant, + connection: ActiveConnection, + msg: dict[str, Any], + entry: ConfigEntry, + client: Client, + driver: Driver, +) -> None: + """Cancel secure bootstrap S2.""" + await driver.controller.async_cancel_secure_bootstrap_s2() + connection.send_result(msg[ID]) + + @websocket_api.require_admin @websocket_api.websocket_command( { diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 357ec29b810..3761ba6eaa6 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -5195,3 +5195,69 @@ async def test_get_integration_settings( assert msg["result"] == { CONF_INSTALLER_MODE: installer_mode, } + + +async def test_cancel_secure_bootstrap_s2( + hass: HomeAssistant, client, integration, hass_ws_client: WebSocketGenerator +) -> None: + """Test that the cancel_secure_bootstrap_s2 WS API call works.""" + entry = integration + ws_client = await hass_ws_client(hass) + + # Test successful cancellation + await ws_client.send_json_auto_id( + { + TYPE: "zwave_js/cancel_secure_bootstrap_s2", + ENTRY_ID: entry.entry_id, + } + ) + msg = await ws_client.receive_json() + assert msg["success"] + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "controller.cancel_secure_bootstrap_s2" + + # Test FailedZWaveCommand is caught + with patch( + f"{CONTROLLER_PATCH_PREFIX}.async_cancel_secure_bootstrap_s2", + side_effect=FailedZWaveCommand("failed_command", 1, "error message"), + ): + await ws_client.send_json_auto_id( + { + TYPE: "zwave_js/cancel_secure_bootstrap_s2", + ENTRY_ID: entry.entry_id, + } + ) + msg = await ws_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "zwave_error" + assert msg["error"]["message"] == "zwave_error: Z-Wave error 1 - error message" + + # Test sending command with not loaded entry fails + await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + await ws_client.send_json_auto_id( + { + TYPE: "zwave_js/cancel_secure_bootstrap_s2", + ENTRY_ID: entry.entry_id, + } + ) + msg = await ws_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == ERR_NOT_LOADED + + # Test sending command with invalid entry ID fails + await ws_client.send_json_auto_id( + { + TYPE: "zwave_js/cancel_secure_bootstrap_s2", + ENTRY_ID: "invalid_entry_id", + } + ) + msg = await ws_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == ERR_NOT_FOUND