diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index b99f62e8bdc..4917653353e 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -25,6 +25,11 @@ DEFAULT_OPTIONS = {CONF_CONTINUOUS: DEFAULT_CONTINUOUS, CONF_DELAY: DEFAULT_DELA MAX_NUM_DEVICES_TO_DISCOVER = 25 +AUTH_HELP_URL_KEY = "auth_help_url" +AUTH_HELP_URL_VALUE = ( + "https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials" +) + async def validate_input(hass: core.HomeAssistant, data): """Validate the user input allows us to connect. @@ -68,11 +73,6 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return OptionsFlowHandler(config_entry) async def async_step_user(self, user_input=None): - """Handle a flow initialized by the user.""" - # This is for backwards compatibility. - return await self.async_step_init(user_input) - - async def async_step_init(self, user_input=None): """Handle a flow start.""" # Check if user chooses manual entry if user_input is not None and not user_input.get(CONF_HOST): @@ -107,7 +107,7 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_manual() return self.async_show_form( - step_id="init", + step_id="user", data_schema=vol.Schema( { vol.Optional("host"): vol.In( @@ -129,6 +129,7 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if user_input is None: return self.async_show_form( step_id="manual", + description_placeholders={AUTH_HELP_URL_KEY: AUTH_HELP_URL_VALUE}, data_schema=vol.Schema( {vol.Required(CONF_HOST): str, vol.Required(CONF_BLID): str} ), @@ -155,9 +156,12 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if user_input is None: return self.async_show_form(step_id="link") - password = await self.hass.async_add_executor_job( - RoombaPassword(self.host).get_password - ) + try: + password = await self.hass.async_add_executor_job( + RoombaPassword(self.host).get_password + ) + except ConnectionRefusedError: + return await self.async_step_link_manual() if not password: return await self.async_step_link_manual() @@ -202,6 +206,7 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form( step_id="link_manual", + description_placeholders={AUTH_HELP_URL_KEY: AUTH_HELP_URL_VALUE}, data_schema=vol.Schema({vol.Required(CONF_PASSWORD): str}), errors=errors, ) diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index 4d0b396d2a9..da2a193d4c9 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -10,7 +10,7 @@ }, "manual": { "title": "Manually connect to the device", - "description": "No Roomba or Braava have been discovered on your network. The BLID is the portion of the device hostname after `iRobot-`. Please follow the steps outlined in the documentation at: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "description": "No Roomba or Braava have been discovered on your network. The BLID is the portion of the device hostname after `iRobot-`. Please follow the steps outlined in the documentation at: {auth_help_url}", "data": { "host": "[%key:common::config_flow::data::host%]", "blid": "BLID" @@ -22,7 +22,7 @@ }, "link_manual": { "title": "Enter Password", - "description": "The password could not be retrivied from the device automaticlly. Please follow the steps outlined in the documentation at: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "description": "The password could not be retrivied from the device automatically. Please follow the steps outlined in the documentation at: {auth_help_url}", "data": { "password": "[%key:common::config_flow::data::password%]" } diff --git a/homeassistant/components/roomba/translations/en.json b/homeassistant/components/roomba/translations/en.json index 4d0b396d2a9..da2a193d4c9 100644 --- a/homeassistant/components/roomba/translations/en.json +++ b/homeassistant/components/roomba/translations/en.json @@ -10,7 +10,7 @@ }, "manual": { "title": "Manually connect to the device", - "description": "No Roomba or Braava have been discovered on your network. The BLID is the portion of the device hostname after `iRobot-`. Please follow the steps outlined in the documentation at: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "description": "No Roomba or Braava have been discovered on your network. The BLID is the portion of the device hostname after `iRobot-`. Please follow the steps outlined in the documentation at: {auth_help_url}", "data": { "host": "[%key:common::config_flow::data::host%]", "blid": "BLID" @@ -22,7 +22,7 @@ }, "link_manual": { "title": "Enter Password", - "description": "The password could not be retrivied from the device automaticlly. Please follow the steps outlined in the documentation at: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "description": "The password could not be retrivied from the device automatically. Please follow the steps outlined in the documentation at: {auth_help_url}", "data": { "password": "[%key:common::config_flow::data::password%]" } diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py index a90cc9c621f..ef236a39e3c 100644 --- a/tests/components/roomba/test_config_flow.py +++ b/tests/components/roomba/test_config_flow.py @@ -65,6 +65,12 @@ def _mocked_failed_getpassword(*_): return roomba_password +def _mocked_connection_refused_on_getpassword(*_): + roomba_password = MagicMock() + roomba_password.get_password = MagicMock(side_effect=ConnectionRefusedError) + return roomba_password + + async def test_form_user_discovery_and_password_fetch(hass): """Test we can discovery and fetch the password.""" await setup.async_setup_component(hass, "persistent_notification", {}) @@ -84,7 +90,7 @@ async def test_form_user_discovery_and_password_fetch(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] is None - assert result["step_id"] == "init" + assert result["step_id"] == "user" result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -195,7 +201,7 @@ async def test_form_user_discovery_manual_and_auto_password_fetch(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] is None - assert result["step_id"] == "init" + assert result["step_id"] == "user" result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -268,7 +274,7 @@ async def test_form_user_discovery_manual_and_auto_password_fetch_but_cannot_con assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] is None - assert result["step_id"] == "init" + assert result["step_id"] == "user" result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -504,3 +510,72 @@ async def test_form_user_discovery_fails_and_password_fetch_fails_and_cannot_con assert result4["errors"] == {"base": "cannot_connect"} assert len(mock_setup.mock_calls) == 0 assert len(mock_setup_entry.mock_calls) == 0 + + +async def test_form_user_discovery_and_password_fetch_gets_connection_refused(hass): + """Test we can discovery and fetch the password manually.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + mocked_roomba = _create_mocked_roomba( + roomba_connected=True, + master_state={"state": {"reported": {"name": "myroomba"}}}, + ) + + with patch( + "homeassistant.components.roomba.config_flow.RoombaDiscovery", _mocked_discovery + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] is None + assert result["step_id"] == "user" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: MOCK_IP}, + ) + await hass.async_block_till_done() + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["errors"] is None + assert result2["step_id"] == "link" + + with patch( + "homeassistant.components.roomba.config_flow.RoombaPassword", + _mocked_connection_refused_on_getpassword, + ): + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {}, + ) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.roomba.config_flow.Roomba", + return_value=mocked_roomba, + ), patch( + "homeassistant.components.roomba.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.roomba.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result4 = await hass.config_entries.flow.async_configure( + result3["flow_id"], + {CONF_PASSWORD: "password"}, + ) + await hass.async_block_till_done() + + assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result4["title"] == "myroomba" + assert result4["result"].unique_id == "blid" + assert result4["data"] == { + CONF_BLID: "blid", + CONF_CONTINUOUS: True, + CONF_DELAY: 1, + CONF_HOST: MOCK_IP, + CONF_PASSWORD: "password", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1