Add Supervisor discovery to motionEye (#50901)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Franck Nijhof 2021-05-26 17:40:07 +02:00 committed by GitHub
parent 18e6ae8750
commit 19c505c0f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 189 additions and 4 deletions

View File

@ -32,6 +32,7 @@ class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for motionEye."""
VERSION = 1
_hassio_discovery: dict[str, Any] | None = None
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@ -42,13 +43,18 @@ class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN):
user_input: dict[str, Any], errors: dict[str, str] | None = None
) -> FlowResult:
"""Show the form to the user."""
url_schema: dict[vol.Required, type[str]] = {}
if not self._hassio_discovery:
# Only ask for URL when not discovered
url_schema[
vol.Required(CONF_URL, default=user_input.get(CONF_URL, ""))
] = str
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(
CONF_URL, default=user_input.get(CONF_URL, "")
): str,
**url_schema,
vol.Optional(
CONF_ADMIN_USERNAME,
default=user_input.get(CONF_ADMIN_USERNAME),
@ -81,6 +87,10 @@ class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN):
cast(Dict[str, Any], reauth_entry.data) if reauth_entry else {}
)
if self._hassio_discovery:
# In case of Supervisor discovery, use pushed URL
user_input[CONF_URL] = self._hassio_discovery[CONF_URL]
try:
# Cannot use cv.url validation in the schema itself, so
# apply extra validation here.
@ -123,8 +133,12 @@ class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN):
# at least prevent entries with the same motionEye URL.
self._async_abort_entries_match({CONF_URL: user_input[CONF_URL]})
title = user_input[CONF_URL]
if self._hassio_discovery:
title = "Add-on"
return self.async_create_entry(
title=f"{user_input[CONF_URL]}",
title=title,
data=user_input,
)
@ -134,3 +148,22 @@ class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN):
) -> FlowResult:
"""Handle a reauthentication flow."""
return await self.async_step_user(config_data)
async def async_step_hassio(self, discovery_info: dict[str, Any]) -> FlowResult:
"""Handle Supervisor discovery."""
self._hassio_discovery = discovery_info
await self._async_handle_discovery_without_unique_id()
return await self.async_step_hassio_confirm()
async def async_step_hassio_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Confirm Supervisor discovery."""
if user_input is None and self._hassio_discovery is not None:
return self.async_show_form(
step_id="hassio_confirm",
description_placeholders={"addon": self._hassio_discovery["addon"]},
)
return await self.async_step_user()

View File

@ -9,6 +9,10 @@
"surveillance_username": "Surveillance [%key:common::config_flow::data::username%]",
"surveillance_password": "Surveillance [%key:common::config_flow::data::password%]"
}
},
"hassio_confirm": {
"title": "motionEye via Home Assistant add-on",
"description": "Do you want to configure Home Assistant to connect to the motionEye service provided by the add-on: {addon}?"
}
},
"error": {

View File

@ -11,6 +11,10 @@
"unknown": "Unexpected error"
},
"step": {
"hassio_confirm": {
"description": "Do you want to configure Home Assistant to connect to the motionEye service provided by the add-on: {addon}?",
"title": "motionEye via Home Assistant add-on"
},
"user": {
"data": {
"admin_password": "Admin Password",

View File

@ -69,6 +69,58 @@ async def test_user_success(hass: HomeAssistant) -> None:
assert mock_client.async_client_close.called
async def test_hassio_success(hass: HomeAssistant) -> None:
"""Test successful Supervisor flow."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN,
data={"addon": "motionEye", "url": TEST_URL},
context={"source": config_entries.SOURCE_HASSIO},
)
assert result.get("type") == data_entry_flow.RESULT_TYPE_FORM
assert result.get("step_id") == "hassio_confirm"
assert result.get("description_placeholders") == {"addon": "motionEye"}
assert "flow_id" in result
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result2.get("type") == data_entry_flow.RESULT_TYPE_FORM
assert result2.get("step_id") == "user"
assert "flow_id" in result2
mock_client = create_mock_motioneye_client()
with patch(
"homeassistant.components.motioneye.MotionEyeClient",
return_value=mock_client,
), patch(
"homeassistant.components.motioneye.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{
CONF_ADMIN_USERNAME: "admin-username",
CONF_ADMIN_PASSWORD: "admin-password",
CONF_SURVEILLANCE_USERNAME: "surveillance-username",
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
},
)
await hass.async_block_till_done()
assert result3.get("type") == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result3.get("title") == "Add-on"
assert result3.get("data") == {
CONF_URL: TEST_URL,
CONF_ADMIN_USERNAME: "admin-username",
CONF_ADMIN_PASSWORD: "admin-password",
CONF_SURVEILLANCE_USERNAME: "surveillance-username",
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
}
assert len(mock_setup_entry.mock_calls) == 1
assert mock_client.async_client_close.called
async def test_user_invalid_auth(hass: HomeAssistant) -> None:
"""Test invalid auth is handled correctly."""
result = await hass.config_entries.flow.async_init(
@ -287,3 +339,95 @@ async def test_duplicate(hass: HomeAssistant) -> None:
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
assert mock_client.async_client_close.called
async def test_hassio_already_configured(hass: HomeAssistant) -> None:
"""Test we don't discover when already configured."""
MockConfigEntry(
domain=DOMAIN,
data={CONF_URL: TEST_URL},
).add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
data={"addon": "motionEye", "url": TEST_URL},
context={"source": config_entries.SOURCE_HASSIO},
)
assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT
assert result.get("reason") == "already_configured"
async def test_hassio_ignored(hass: HomeAssistant) -> None:
"""Test Supervisor discovered instance can be ignored."""
MockConfigEntry(domain=DOMAIN, source=config_entries.SOURCE_IGNORE).add_to_hass(
hass
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
data={"addon": "motionEye", "url": TEST_URL},
context={"source": config_entries.SOURCE_HASSIO},
)
assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT
assert result.get("reason") == "already_configured"
async def test_hassio_abort_if_already_in_progress(hass: HomeAssistant) -> None:
"""Test Supervisor discovered flow aborts if user flow in progress."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result.get("type") == data_entry_flow.RESULT_TYPE_FORM
result2 = await hass.config_entries.flow.async_init(
DOMAIN,
data={"addon": "motionEye", "url": TEST_URL},
context={"source": config_entries.SOURCE_HASSIO},
)
assert result2.get("type") == data_entry_flow.RESULT_TYPE_ABORT
assert result2.get("reason") == "already_in_progress"
async def test_hassio_clean_up_on_user_flow(hass: HomeAssistant) -> None:
"""Test Supervisor discovered flow is clean up when doing user flow."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN,
data={"addon": "motionEye", "url": TEST_URL},
context={"source": config_entries.SOURCE_HASSIO},
)
assert result.get("type") == data_entry_flow.RESULT_TYPE_FORM
result2 = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result2.get("type") == data_entry_flow.RESULT_TYPE_FORM
assert "flow_id" in result2
mock_client = create_mock_motioneye_client()
with patch(
"homeassistant.components.motioneye.MotionEyeClient",
return_value=mock_client,
), patch(
"homeassistant.components.motioneye.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{
CONF_URL: TEST_URL,
CONF_ADMIN_USERNAME: "admin-username",
CONF_ADMIN_PASSWORD: "admin-password",
CONF_SURVEILLANCE_USERNAME: "surveillance-username",
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
},
)
await hass.async_block_till_done()
assert result3.get("type") == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert len(mock_setup_entry.mock_calls) == 1
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 0