mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 09:17:10 +00:00
Supplementary fixes to new motionEye integration (#49626)
This commit is contained in:
parent
914451d99c
commit
3077363f44
@ -13,10 +13,10 @@ from motioneye_client.client import (
|
|||||||
from motioneye_client.const import KEY_CAMERAS, KEY_ID, KEY_NAME
|
from motioneye_client.const import KEY_CAMERAS, KEY_ID, KEY_NAME
|
||||||
|
|
||||||
from homeassistant.components.camera.const import DOMAIN as CAMERA_DOMAIN
|
from homeassistant.components.camera.const import DOMAIN as CAMERA_DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_SOURCE, CONF_URL
|
from homeassistant.const import CONF_URL
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.dispatcher import (
|
from homeassistant.helpers.dispatcher import (
|
||||||
async_dispatcher_connect,
|
async_dispatcher_connect,
|
||||||
@ -28,7 +28,6 @@ from .const import (
|
|||||||
CONF_ADMIN_PASSWORD,
|
CONF_ADMIN_PASSWORD,
|
||||||
CONF_ADMIN_USERNAME,
|
CONF_ADMIN_USERNAME,
|
||||||
CONF_CLIENT,
|
CONF_CLIENT,
|
||||||
CONF_CONFIG_ENTRY,
|
|
||||||
CONF_COORDINATOR,
|
CONF_COORDINATOR,
|
||||||
CONF_SURVEILLANCE_PASSWORD,
|
CONF_SURVEILLANCE_PASSWORD,
|
||||||
CONF_SURVEILLANCE_USERNAME,
|
CONF_SURVEILLANCE_USERNAME,
|
||||||
@ -98,22 +97,6 @@ def listen_for_new_cameras(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _create_reauth_flow(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
config_entry: ConfigEntry,
|
|
||||||
) -> None:
|
|
||||||
hass.async_create_task(
|
|
||||||
hass.config_entries.flow.async_init(
|
|
||||||
DOMAIN,
|
|
||||||
context={
|
|
||||||
CONF_SOURCE: SOURCE_REAUTH,
|
|
||||||
CONF_CONFIG_ENTRY: config_entry,
|
|
||||||
},
|
|
||||||
data=config_entry.data,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _add_camera(
|
def _add_camera(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -155,10 +138,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await client.async_client_login()
|
await client.async_client_login()
|
||||||
except MotionEyeClientInvalidAuthError:
|
except MotionEyeClientInvalidAuthError as exc:
|
||||||
await client.async_client_close()
|
await client.async_client_close()
|
||||||
await _create_reauth_flow(hass, entry)
|
raise ConfigEntryAuthFailed from exc
|
||||||
return False
|
|
||||||
except MotionEyeClientError as exc:
|
except MotionEyeClientError as exc:
|
||||||
await client.async_client_close()
|
await client.async_client_close()
|
||||||
raise ConfigEntryNotReady from exc
|
raise ConfigEntryNotReady from exc
|
||||||
|
@ -59,7 +59,7 @@ PLATFORMS = ["camera"]
|
|||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: Callable
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: Callable
|
||||||
) -> bool:
|
) -> None:
|
||||||
"""Set up motionEye from a config entry."""
|
"""Set up motionEye from a config entry."""
|
||||||
entry_data = hass.data[DOMAIN][entry.entry_id]
|
entry_data = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
@ -82,7 +82,6 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
|
|
||||||
listen_for_new_cameras(hass, entry, camera_add)
|
listen_for_new_cameras(hass, entry, camera_add)
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class MotionEyeMjpegCamera(MjpegCamera, CoordinatorEntity):
|
class MotionEyeMjpegCamera(MjpegCamera, CoordinatorEntity):
|
||||||
@ -96,7 +95,7 @@ class MotionEyeMjpegCamera(MjpegCamera, CoordinatorEntity):
|
|||||||
camera: dict[str, Any],
|
camera: dict[str, Any],
|
||||||
client: MotionEyeClient,
|
client: MotionEyeClient,
|
||||||
coordinator: DataUpdateCoordinator,
|
coordinator: DataUpdateCoordinator,
|
||||||
):
|
) -> None:
|
||||||
"""Initialize a MJPEG camera."""
|
"""Initialize a MJPEG camera."""
|
||||||
self._surveillance_username = username
|
self._surveillance_username = username
|
||||||
self._surveillance_password = password
|
self._surveillance_password = password
|
||||||
@ -109,7 +108,7 @@ class MotionEyeMjpegCamera(MjpegCamera, CoordinatorEntity):
|
|||||||
config_entry_id, self._camera_id, TYPE_MOTIONEYE_MJPEG_CAMERA
|
config_entry_id, self._camera_id, TYPE_MOTIONEYE_MJPEG_CAMERA
|
||||||
)
|
)
|
||||||
self._motion_detection_enabled: bool = camera.get(KEY_MOTION_DETECTION, False)
|
self._motion_detection_enabled: bool = camera.get(KEY_MOTION_DETECTION, False)
|
||||||
self._available = MotionEyeMjpegCamera._is_acceptable_streaming_camera(camera)
|
self._available = self._is_acceptable_streaming_camera(camera)
|
||||||
|
|
||||||
# motionEye cameras are always streaming or unavailable.
|
# motionEye cameras are always streaming or unavailable.
|
||||||
self.is_streaming = True
|
self.is_streaming = True
|
||||||
@ -184,7 +183,7 @@ class MotionEyeMjpegCamera(MjpegCamera, CoordinatorEntity):
|
|||||||
available = False
|
available = False
|
||||||
if self.coordinator.last_update_success:
|
if self.coordinator.last_update_success:
|
||||||
camera = get_camera_from_cameras(self._camera_id, self.coordinator.data)
|
camera = get_camera_from_cameras(self._camera_id, self.coordinator.data)
|
||||||
if MotionEyeMjpegCamera._is_acceptable_streaming_camera(camera):
|
if self._is_acceptable_streaming_camera(camera):
|
||||||
assert camera
|
assert camera
|
||||||
self._set_mjpeg_camera_state_for_camera(camera)
|
self._set_mjpeg_camera_state_for_camera(camera)
|
||||||
self._motion_detection_enabled = camera.get(KEY_MOTION_DETECTION, False)
|
self._motion_detection_enabled = camera.get(KEY_MOTION_DETECTION, False)
|
||||||
|
@ -24,7 +24,6 @@ from . import create_motioneye_client
|
|||||||
from .const import (
|
from .const import (
|
||||||
CONF_ADMIN_PASSWORD,
|
CONF_ADMIN_PASSWORD,
|
||||||
CONF_ADMIN_USERNAME,
|
CONF_ADMIN_USERNAME,
|
||||||
CONF_CONFIG_ENTRY,
|
|
||||||
CONF_SURVEILLANCE_PASSWORD,
|
CONF_SURVEILLANCE_PASSWORD,
|
||||||
CONF_SURVEILLANCE_USERNAME,
|
CONF_SURVEILLANCE_USERNAME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -43,81 +42,96 @@ class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
self, user_input: ConfigType | None = None
|
self, user_input: ConfigType | None = None
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Handle the initial step."""
|
"""Handle the initial step."""
|
||||||
out: dict[str, Any] = {}
|
|
||||||
errors = {}
|
def _get_form(
|
||||||
|
user_input: ConfigType, errors: dict[str, str] | None = None
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Show the form to the user."""
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(
|
||||||
|
CONF_URL, default=user_input.get(CONF_URL, "")
|
||||||
|
): str,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_ADMIN_USERNAME,
|
||||||
|
default=user_input.get(CONF_ADMIN_USERNAME),
|
||||||
|
): str,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_ADMIN_PASSWORD,
|
||||||
|
default=user_input.get(CONF_ADMIN_PASSWORD),
|
||||||
|
): str,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_SURVEILLANCE_USERNAME,
|
||||||
|
default=user_input.get(CONF_SURVEILLANCE_USERNAME),
|
||||||
|
): str,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_SURVEILLANCE_PASSWORD,
|
||||||
|
default=user_input.get(CONF_SURVEILLANCE_PASSWORD),
|
||||||
|
): str,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
reauth_entry = None
|
||||||
|
if self.context.get("entry_id"):
|
||||||
|
reauth_entry = self.hass.config_entries.async_get_entry(
|
||||||
|
self.context["entry_id"]
|
||||||
|
)
|
||||||
|
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
entry = self.context.get(CONF_CONFIG_ENTRY)
|
return _get_form(reauth_entry.data if reauth_entry else {})
|
||||||
user_input = entry.data if entry else {}
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
# Cannot use cv.url validation in the schema itself, so
|
|
||||||
# apply extra validation here.
|
|
||||||
cv.url(user_input[CONF_URL])
|
|
||||||
except vol.Invalid:
|
|
||||||
errors["base"] = "invalid_url"
|
|
||||||
else:
|
|
||||||
client = create_motioneye_client(
|
|
||||||
user_input[CONF_URL],
|
|
||||||
admin_username=user_input.get(CONF_ADMIN_USERNAME),
|
|
||||||
admin_password=user_input.get(CONF_ADMIN_PASSWORD),
|
|
||||||
surveillance_username=user_input.get(CONF_SURVEILLANCE_USERNAME),
|
|
||||||
surveillance_password=user_input.get(CONF_SURVEILLANCE_PASSWORD),
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await client.async_client_login()
|
# Cannot use cv.url validation in the schema itself, so
|
||||||
except MotionEyeClientConnectionError:
|
# apply extra validation here.
|
||||||
errors["base"] = "cannot_connect"
|
cv.url(user_input[CONF_URL])
|
||||||
except MotionEyeClientInvalidAuthError:
|
except vol.Invalid:
|
||||||
errors["base"] = "invalid_auth"
|
return _get_form(user_input, {"base": "invalid_url"})
|
||||||
except MotionEyeClientRequestError:
|
|
||||||
errors["base"] = "unknown"
|
|
||||||
else:
|
|
||||||
entry = self.context.get(CONF_CONFIG_ENTRY)
|
|
||||||
if (
|
|
||||||
self.context.get(CONF_SOURCE) == SOURCE_REAUTH
|
|
||||||
and entry is not None
|
|
||||||
):
|
|
||||||
self.hass.config_entries.async_update_entry(
|
|
||||||
entry, data=user_input
|
|
||||||
)
|
|
||||||
# Need to manually reload, as the listener won't have been
|
|
||||||
# installed because the initial load did not succeed (the reauth
|
|
||||||
# flow will not be initiated if the load succeeds).
|
|
||||||
await self.hass.config_entries.async_reload(entry.entry_id)
|
|
||||||
out = self.async_abort(reason="reauth_successful")
|
|
||||||
return out
|
|
||||||
|
|
||||||
out = self.async_create_entry(
|
client = create_motioneye_client(
|
||||||
title=f"{user_input[CONF_URL]}",
|
user_input[CONF_URL],
|
||||||
data=user_input,
|
admin_username=user_input.get(CONF_ADMIN_USERNAME),
|
||||||
)
|
admin_password=user_input.get(CONF_ADMIN_PASSWORD),
|
||||||
return out
|
surveillance_username=user_input.get(CONF_SURVEILLANCE_USERNAME),
|
||||||
|
surveillance_password=user_input.get(CONF_SURVEILLANCE_PASSWORD),
|
||||||
out = self.async_show_form(
|
)
|
||||||
step_id="user",
|
|
||||||
data_schema=vol.Schema(
|
errors = {}
|
||||||
{
|
try:
|
||||||
vol.Required(CONF_URL, default=user_input.get(CONF_URL, "")): str,
|
await client.async_client_login()
|
||||||
vol.Optional(
|
except MotionEyeClientConnectionError:
|
||||||
CONF_ADMIN_USERNAME, default=user_input.get(CONF_ADMIN_USERNAME)
|
errors["base"] = "cannot_connect"
|
||||||
): str,
|
except MotionEyeClientInvalidAuthError:
|
||||||
vol.Optional(
|
errors["base"] = "invalid_auth"
|
||||||
CONF_ADMIN_PASSWORD, default=user_input.get(CONF_ADMIN_PASSWORD)
|
except MotionEyeClientRequestError:
|
||||||
): str,
|
errors["base"] = "unknown"
|
||||||
vol.Optional(
|
finally:
|
||||||
CONF_SURVEILLANCE_USERNAME,
|
await client.async_client_close()
|
||||||
default=user_input.get(CONF_SURVEILLANCE_USERNAME),
|
|
||||||
): str,
|
if errors:
|
||||||
vol.Optional(
|
return _get_form(user_input, errors)
|
||||||
CONF_SURVEILLANCE_PASSWORD,
|
|
||||||
default=user_input.get(CONF_SURVEILLANCE_PASSWORD),
|
if self.context.get(CONF_SOURCE) == SOURCE_REAUTH and reauth_entry is not None:
|
||||||
): str,
|
self.hass.config_entries.async_update_entry(reauth_entry, data=user_input)
|
||||||
}
|
# Need to manually reload, as the listener won't have been
|
||||||
),
|
# installed because the initial load did not succeed (the reauth
|
||||||
errors=errors,
|
# flow will not be initiated if the load succeeds).
|
||||||
|
await self.hass.config_entries.async_reload(reauth_entry.entry_id)
|
||||||
|
return self.async_abort(reason="reauth_successful")
|
||||||
|
|
||||||
|
# Search for duplicates: there isn't a useful unique_id, but
|
||||||
|
# at least prevent entries with the same motionEye URL.
|
||||||
|
for existing_entry in self._async_current_entries(include_ignore=False):
|
||||||
|
if existing_entry.data.get(CONF_URL) == user_input[CONF_URL]:
|
||||||
|
return self.async_abort(reason="already_configured")
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=f"{user_input[CONF_URL]}",
|
||||||
|
data=user_input,
|
||||||
)
|
)
|
||||||
return out
|
|
||||||
|
|
||||||
async def async_step_reauth(
|
async def async_step_reauth(
|
||||||
self,
|
self,
|
||||||
|
@ -3,7 +3,6 @@ from datetime import timedelta
|
|||||||
|
|
||||||
DOMAIN = "motioneye"
|
DOMAIN = "motioneye"
|
||||||
|
|
||||||
CONF_CONFIG_ENTRY = "config_entry"
|
|
||||||
CONF_CLIENT = "client"
|
CONF_CLIENT = "client"
|
||||||
CONF_COORDINATOR = "coordinator"
|
CONF_COORDINATOR = "coordinator"
|
||||||
CONF_ADMIN_PASSWORD = "admin_password"
|
CONF_ADMIN_PASSWORD = "admin_password"
|
||||||
|
@ -12,7 +12,6 @@ from homeassistant import config_entries, data_entry_flow, setup
|
|||||||
from homeassistant.components.motioneye.const import (
|
from homeassistant.components.motioneye.const import (
|
||||||
CONF_ADMIN_PASSWORD,
|
CONF_ADMIN_PASSWORD,
|
||||||
CONF_ADMIN_USERNAME,
|
CONF_ADMIN_USERNAME,
|
||||||
CONF_CONFIG_ENTRY,
|
|
||||||
CONF_SURVEILLANCE_PASSWORD,
|
CONF_SURVEILLANCE_PASSWORD,
|
||||||
CONF_SURVEILLANCE_USERNAME,
|
CONF_SURVEILLANCE_USERNAME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -22,6 +21,8 @@ from homeassistant.core import HomeAssistant
|
|||||||
|
|
||||||
from . import TEST_URL, create_mock_motioneye_client, create_mock_motioneye_config_entry
|
from . import TEST_URL, create_mock_motioneye_client, create_mock_motioneye_config_entry
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ async def test_user_success(hass: HomeAssistant) -> None:
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["errors"] == {}
|
assert not result["errors"]
|
||||||
|
|
||||||
mock_client = create_mock_motioneye_client()
|
mock_client = create_mock_motioneye_client()
|
||||||
|
|
||||||
@ -65,6 +66,7 @@ async def test_user_success(hass: HomeAssistant) -> None:
|
|||||||
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
|
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
|
||||||
}
|
}
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
assert mock_client.async_client_close.called
|
||||||
|
|
||||||
|
|
||||||
async def test_user_invalid_auth(hass: HomeAssistant) -> None:
|
async def test_user_invalid_auth(hass: HomeAssistant) -> None:
|
||||||
@ -92,10 +94,11 @@ async def test_user_invalid_auth(hass: HomeAssistant) -> None:
|
|||||||
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
|
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await mock_client.async_client_close()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["errors"] == {"base": "invalid_auth"}
|
assert result["errors"] == {"base": "invalid_auth"}
|
||||||
|
assert mock_client.async_client_close.called
|
||||||
|
|
||||||
|
|
||||||
async def test_user_invalid_url(hass: HomeAssistant) -> None:
|
async def test_user_invalid_url(hass: HomeAssistant) -> None:
|
||||||
@ -105,9 +108,10 @@ async def test_user_invalid_url(hass: HomeAssistant) -> None:
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mock_client = create_mock_motioneye_client()
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.motioneye.MotionEyeClient",
|
"homeassistant.components.motioneye.MotionEyeClient",
|
||||||
return_value=create_mock_motioneye_client(),
|
return_value=mock_client,
|
||||||
):
|
):
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
@ -119,6 +123,7 @@ async def test_user_invalid_url(hass: HomeAssistant) -> None:
|
|||||||
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
|
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["errors"] == {"base": "invalid_url"}
|
assert result["errors"] == {"base": "invalid_url"}
|
||||||
@ -149,10 +154,11 @@ async def test_user_cannot_connect(hass: HomeAssistant) -> None:
|
|||||||
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
|
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await mock_client.async_client_close()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["errors"] == {"base": "cannot_connect"}
|
assert result["errors"] == {"base": "cannot_connect"}
|
||||||
|
assert mock_client.async_client_close.called
|
||||||
|
|
||||||
|
|
||||||
async def test_user_request_error(hass: HomeAssistant) -> None:
|
async def test_user_request_error(hass: HomeAssistant) -> None:
|
||||||
@ -178,10 +184,11 @@ async def test_user_request_error(hass: HomeAssistant) -> None:
|
|||||||
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
|
CONF_SURVEILLANCE_PASSWORD: "surveillance-password",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await mock_client.async_client_close()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["errors"] == {"base": "unknown"}
|
assert result["errors"] == {"base": "unknown"}
|
||||||
|
assert mock_client.async_client_close.called
|
||||||
|
|
||||||
|
|
||||||
async def test_reauth(hass: HomeAssistant) -> None:
|
async def test_reauth(hass: HomeAssistant) -> None:
|
||||||
@ -197,11 +204,11 @@ async def test_reauth(hass: HomeAssistant) -> None:
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={
|
context={
|
||||||
"source": config_entries.SOURCE_REAUTH,
|
"source": config_entries.SOURCE_REAUTH,
|
||||||
CONF_CONFIG_ENTRY: config_entry,
|
"entry_id": config_entry.entry_id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["errors"] == {}
|
assert not result["errors"]
|
||||||
|
|
||||||
mock_client = create_mock_motioneye_client()
|
mock_client = create_mock_motioneye_client()
|
||||||
|
|
||||||
@ -226,8 +233,57 @@ async def test_reauth(hass: HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
assert result["reason"] == "reauth_successful"
|
assert result["reason"] == "reauth_successful"
|
||||||
assert config_entry.data == new_data
|
assert config_entry.data == new_data
|
||||||
|
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
assert mock_client.async_client_close.called
|
||||||
|
|
||||||
|
|
||||||
|
async def test_duplicate(hass: HomeAssistant) -> None:
|
||||||
|
"""Test that a duplicate entry (same URL) is rejected."""
|
||||||
|
config_data = {
|
||||||
|
CONF_URL: TEST_URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add an existing entry with the same URL.
|
||||||
|
existing_entry: MockConfigEntry = MockConfigEntry( # type: ignore[no-untyped-call]
|
||||||
|
domain=DOMAIN,
|
||||||
|
data=config_data,
|
||||||
|
)
|
||||||
|
existing_entry.add_to_hass(hass) # type: ignore[no-untyped-call]
|
||||||
|
|
||||||
|
# Now do the usual config entry process, and verify it is rejected.
|
||||||
|
create_mock_motioneye_config_entry(hass, data=config_data)
|
||||||
|
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert not result["errors"]
|
||||||
|
mock_client = create_mock_motioneye_client()
|
||||||
|
|
||||||
|
new_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",
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.motioneye.MotionEyeClient",
|
||||||
|
return_value=mock_client,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
new_data,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
assert mock_client.async_client_close.called
|
||||||
|
Loading…
x
Reference in New Issue
Block a user