Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions homeassistant/components/motioneye/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for motionEye."""

VERSION = 1
_hassio_discovery: dict[str, Any] | None = None
Comment thread
frenck marked this conversation as resolved.

async def async_step_user(
self, user_input: dict[str, Any] | None = None
Expand All @@ -42,13 +43,18 @@ def _get_form(
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]] = {}
Comment thread
frenck marked this conversation as resolved.
if not self._hassio_discovery:
# Only ask for URL when not discovered
url_schema.update(
{vol.Required(CONF_URL, default=user_input.get(CONF_URL, "")): str}
)
Comment thread
frenck marked this conversation as resolved.
Outdated

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),
Expand Down Expand Up @@ -81,6 +87,10 @@ def _get_form(
cast(Dict[str, Any], reauth_entry.data) if reauth_entry else {}
)

if self._hassio_discovery:
Comment thread
frenck marked this conversation as resolved.
# 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.
Expand Down Expand Up @@ -123,8 +133,12 @@ def _get_form(
# 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,
)

Expand All @@ -134,3 +148,22 @@ async def async_step_reauth(
) -> 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:
Comment thread
frenck marked this conversation as resolved.
"""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()
4 changes: 4 additions & 0 deletions homeassistant/components/motioneye/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}?"
Comment thread
frenck marked this conversation as resolved.
}
},
"error": {
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/motioneye/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
144 changes: 144 additions & 0 deletions tests/components/motioneye/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Comment thread
frenck marked this conversation as resolved.
"""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