Refactor EZVIZ config flow tests (#136434)

This commit is contained in:
Joost Lekkerkerker 2025-01-25 10:43:22 +01:00 committed by GitHub
parent 28951096a8
commit fb04c256a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 510 additions and 526 deletions

View File

@ -1,102 +1,13 @@
"""Tests for the EZVIZ integration.""" """Tests for the EZVIZ integration."""
from unittest.mock import _patch, patch
from homeassistant.components.ezviz.const import (
ATTR_SERIAL,
ATTR_TYPE_CAMERA,
ATTR_TYPE_CLOUD,
CONF_FFMPEG_ARGUMENTS,
CONF_RFSESSION_ID,
CONF_SESSION_ID,
DEFAULT_FFMPEG_ARGUMENTS,
DEFAULT_TIMEOUT,
DOMAIN,
)
from homeassistant.const import (
CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_TIMEOUT,
CONF_TYPE,
CONF_URL,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
ENTRY_CONFIG = {
CONF_SESSION_ID: "test-username",
CONF_RFSESSION_ID: "test-password",
CONF_URL: "apiieu.ezvizlife.com",
CONF_TYPE: ATTR_TYPE_CLOUD,
}
ENTRY_OPTIONS = { async def setup_integration(hass: HomeAssistant, entry: MockConfigEntry) -> None:
CONF_FFMPEG_ARGUMENTS: DEFAULT_FFMPEG_ARGUMENTS,
CONF_TIMEOUT: DEFAULT_TIMEOUT,
}
USER_INPUT_VALIDATE = {
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
CONF_URL: "apiieu.ezvizlife.com",
}
USER_INPUT = {
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
CONF_URL: "apiieu.ezvizlife.com",
CONF_TYPE: ATTR_TYPE_CLOUD,
}
USER_INPUT_CAMERA_VALIDATE = {
ATTR_SERIAL: "C666666",
CONF_PASSWORD: "test-password",
CONF_USERNAME: "test-username",
}
USER_INPUT_CAMERA = {
CONF_PASSWORD: "test-password",
CONF_USERNAME: "test-username",
CONF_TYPE: ATTR_TYPE_CAMERA,
}
DISCOVERY_INFO = {
ATTR_SERIAL: "C666666",
CONF_USERNAME: None,
CONF_PASSWORD: None,
CONF_IP_ADDRESS: "127.0.0.1",
}
TEST = {
CONF_USERNAME: None,
CONF_PASSWORD: None,
CONF_IP_ADDRESS: "127.0.0.1",
}
API_LOGIN_RETURN_VALIDATE = {
CONF_SESSION_ID: "fake_token",
CONF_RFSESSION_ID: "fake_rf_token",
CONF_URL: "apiieu.ezvizlife.com",
CONF_TYPE: ATTR_TYPE_CLOUD,
}
def patch_async_setup_entry() -> _patch:
"""Patch async_setup_entry."""
return patch(
"homeassistant.components.ezviz.async_setup_entry",
return_value=True,
)
async def init_integration(hass: HomeAssistant) -> MockConfigEntry:
"""Set up the EZVIZ integration in Home Assistant.""" """Set up the EZVIZ integration in Home Assistant."""
entry = MockConfigEntry(domain=DOMAIN, data=ENTRY_CONFIG, options=ENTRY_OPTIONS)
entry.add_to_hass(hass) entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
return entry

View File

@ -1,19 +1,30 @@
"""Define pytest.fixtures available for all tests.""" """Define pytest.fixtures available for all tests."""
from collections.abc import Generator from collections.abc import Generator
from unittest.mock import MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
from pyezviz import EzvizClient
from pyezviz.test_cam_rtsp import TestRTSPAuth
import pytest import pytest
from homeassistant.components.ezviz import (
ATTR_TYPE_CAMERA,
ATTR_TYPE_CLOUD,
CONF_RFSESSION_ID,
CONF_SESSION_ID,
DOMAIN,
)
from homeassistant.const import CONF_PASSWORD, CONF_TYPE, CONF_URL, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
ezviz_login_token_return = { from tests.common import MockConfigEntry
"session_id": "fake_token",
"rf_session_id": "fake_rf_token",
"api_url": "apiieu.ezvizlife.com", @pytest.fixture
} def mock_setup_entry() -> Generator[AsyncMock]:
"""Mock setting up a config entry."""
with patch(
"homeassistant.components.ezviz.async_setup_entry", return_value=True
) as setup_entry_mock:
yield setup_entry_mock
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
@ -23,40 +34,67 @@ def mock_ffmpeg(hass: HomeAssistant) -> None:
@pytest.fixture @pytest.fixture
def ezviz_test_rtsp_config_flow() -> Generator[MagicMock]: def mock_config_entry() -> MockConfigEntry:
"""Return the default mocked config entry."""
return MockConfigEntry(
domain=DOMAIN,
unique_id="test-username",
title="test-username",
data={
CONF_SESSION_ID: "test-username",
CONF_RFSESSION_ID: "test-password",
CONF_URL: "apiieu.ezvizlife.com",
CONF_TYPE: ATTR_TYPE_CLOUD,
},
)
@pytest.fixture
def mock_camera_config_entry() -> MockConfigEntry:
"""Return the default mocked config entry."""
return MockConfigEntry(
domain=DOMAIN,
unique_id="C666666",
title="Camera 1",
data={
CONF_TYPE: ATTR_TYPE_CAMERA,
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
},
)
@pytest.fixture
def mock_ezviz_client() -> Generator[AsyncMock]:
"""Mock the EzvizAPI for easier testing."""
with (
patch(
"homeassistant.components.ezviz.EzvizClient", autospec=True
) as mock_ezviz,
patch("homeassistant.components.ezviz.config_flow.EzvizClient", new=mock_ezviz),
):
instance = mock_ezviz.return_value
instance.login.return_value = {
"session_id": "fake_token",
"rf_session_id": "fake_rf_token",
"api_url": "apiieu.ezvizlife.com",
}
instance.get_detection_sensibility.return_value = True
yield instance
@pytest.fixture
def mock_test_rtsp_auth() -> Generator[MagicMock]:
"""Mock the EzvizApi for easier testing.""" """Mock the EzvizApi for easier testing."""
with ( with (
patch.object(TestRTSPAuth, "main", return_value=True),
patch( patch(
"homeassistant.components.ezviz.config_flow.TestRTSPAuth" "homeassistant.components.ezviz.config_flow.TestRTSPAuth"
) as mock_ezviz_test_rtsp, ) as mock_ezviz_test_rtsp,
): ):
instance = mock_ezviz_test_rtsp.return_value = TestRTSPAuth( instance = mock_ezviz_test_rtsp.return_value
"test-ip",
"test-username",
"test-password",
)
instance.main = MagicMock(return_value=True) instance.main.return_value = True
yield mock_ezviz_test_rtsp yield instance
@pytest.fixture
def ezviz_config_flow() -> Generator[MagicMock]:
"""Mock the EzvizAPI for easier config flow testing."""
with (
patch.object(EzvizClient, "login", return_value=True),
patch("homeassistant.components.ezviz.config_flow.EzvizClient") as mock_ezviz,
):
instance = mock_ezviz.return_value = EzvizClient(
"test-username",
"test-password",
"local.host",
"1",
)
instance.login = MagicMock(return_value=ezviz_login_token_return)
instance.get_detection_sensibility = MagicMock(return_value=True)
yield mock_ezviz

View File

@ -1,11 +1,9 @@
"""Test the EZVIZ config flow.""" """Test the EZVIZ config flow."""
from unittest.mock import MagicMock, patch from unittest.mock import AsyncMock
from pyezviz.exceptions import ( from pyezviz.exceptions import (
AuthTestResultFailed,
EzvizAuthVerificationCode, EzvizAuthVerificationCode,
HTTPError,
InvalidHost, InvalidHost,
InvalidURL, InvalidURL,
PyEzvizError, PyEzvizError,
@ -15,7 +13,10 @@ import pytest
from homeassistant.components.ezviz.const import ( from homeassistant.components.ezviz.const import (
ATTR_SERIAL, ATTR_SERIAL,
ATTR_TYPE_CAMERA, ATTR_TYPE_CAMERA,
ATTR_TYPE_CLOUD,
CONF_FFMPEG_ARGUMENTS, CONF_FFMPEG_ARGUMENTS,
CONF_RFSESSION_ID,
CONF_SESSION_ID,
DEFAULT_FFMPEG_ARGUMENTS, DEFAULT_FFMPEG_ARGUMENTS,
DEFAULT_TIMEOUT, DEFAULT_TIMEOUT,
DOMAIN, DOMAIN,
@ -33,20 +34,14 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from . import ( from . import setup_integration
API_LOGIN_RETURN_VALIDATE,
DISCOVERY_INFO,
USER_INPUT_VALIDATE,
init_integration,
patch_async_setup_entry,
)
from tests.common import MockConfigEntry, start_reauth_flow from tests.common import MockConfigEntry
@pytest.mark.usefixtures("ezviz_config_flow") @pytest.mark.usefixtures("mock_ezviz_client")
async def test_user_form(hass: HomeAssistant) -> None: async def test_full_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test the user initiated form.""" """Test the full flow."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
@ -55,28 +50,32 @@ async def test_user_form(hass: HomeAssistant) -> None:
assert result["step_id"] == "user" assert result["step_id"] == "user"
assert result["errors"] == {} assert result["errors"] == {}
with patch_async_setup_entry() as mock_setup_entry:
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT_VALIDATE, {
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
CONF_URL: "apiieu.ezvizlife.com",
},
) )
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "test-username" assert result["title"] == "test-username"
assert result["data"] == {**API_LOGIN_RETURN_VALIDATE} assert result["data"] == {
CONF_SESSION_ID: "fake_token",
CONF_RFSESSION_ID: "fake_rf_token",
CONF_URL: "apiieu.ezvizlife.com",
CONF_TYPE: ATTR_TYPE_CLOUD,
}
assert result["result"].unique_id == "test-username"
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured_account"
@pytest.mark.usefixtures("mock_ezviz_client")
@pytest.mark.usefixtures("ezviz_config_flow") async def test_user_custom_url(
async def test_user_custom_url(hass: HomeAssistant) -> None: hass: HomeAssistant, mock_setup_entry: AsyncMock
) -> None:
"""Test custom url step.""" """Test custom url step."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
@ -95,45 +94,30 @@ async def test_user_custom_url(hass: HomeAssistant) -> None:
assert result["step_id"] == "user_custom_url" assert result["step_id"] == "user_custom_url"
assert result["errors"] == {} assert result["errors"] == {}
with patch_async_setup_entry() as mock_setup_entry:
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{CONF_URL: "test-user"}, {CONF_URL: "test-user"},
) )
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == API_LOGIN_RETURN_VALIDATE assert result["data"] == {
CONF_SESSION_ID: "fake_token",
CONF_RFSESSION_ID: "fake_rf_token",
CONF_URL: "apiieu.ezvizlife.com",
CONF_TYPE: ATTR_TYPE_CLOUD,
}
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.usefixtures("ezviz_config_flow") @pytest.mark.usefixtures("mock_ezviz_client", "mock_setup_entry")
async def test_async_step_reauth(hass: HomeAssistant) -> None: async def test_async_step_reauth(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test the reauth step.""" """Test the reauth step."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await mock_config_entry.start_reauth_flow(hass)
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
with patch_async_setup_entry() as mock_setup_entry:
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
USER_INPUT_VALIDATE,
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "test-username"
assert result["data"] == {**API_LOGIN_RETURN_VALIDATE}
assert len(mock_setup_entry.mock_calls) == 1
new_entry = hass.config_entries.async_entries(DOMAIN)[0]
result = await start_reauth_flow(hass, new_entry)
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {} assert result["errors"] == {}
@ -145,19 +129,26 @@ async def test_async_step_reauth(hass: HomeAssistant) -> None:
CONF_PASSWORD: "test-password", CONF_PASSWORD: "test-password",
}, },
) )
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful" assert result["reason"] == "reauth_successful"
@pytest.mark.usefixtures("mock_ezviz_client")
async def test_step_discovery_abort_if_cloud_account_missing( async def test_step_discovery_abort_if_cloud_account_missing(
hass: HomeAssistant, hass: HomeAssistant, mock_test_rtsp_auth: AsyncMock
) -> None: ) -> None:
"""Test discovery and confirm step, abort if cloud account was removed.""" """Test discovery and confirm step, abort if cloud account was removed."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_INTEGRATION_DISCOVERY}, data=DISCOVERY_INFO DOMAIN,
context={"source": SOURCE_INTEGRATION_DISCOVERY},
data={
ATTR_SERIAL: "C666666",
CONF_USERNAME: None,
CONF_PASSWORD: None,
CONF_IP_ADDRESS: "127.0.0.1",
},
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm" assert result["step_id"] == "confirm"
@ -170,37 +161,45 @@ async def test_step_discovery_abort_if_cloud_account_missing(
CONF_PASSWORD: "test-pass", CONF_PASSWORD: "test-pass",
}, },
) )
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "ezviz_cloud_account_missing" assert result["reason"] == "ezviz_cloud_account_missing"
async def test_step_reauth_abort_if_cloud_account_missing(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("mock_ezviz_client", "mock_test_rtsp_auth")
async def test_step_reauth_abort_if_cloud_account_missing(
hass: HomeAssistant, mock_camera_config_entry: MockConfigEntry
) -> None:
"""Test reauth and confirm step, abort if cloud account was removed.""" """Test reauth and confirm step, abort if cloud account was removed."""
entry = MockConfigEntry(domain=DOMAIN, data=USER_INPUT_VALIDATE) mock_camera_config_entry.add_to_hass(hass)
entry.add_to_hass(hass)
result = await entry.start_reauth_flow(hass) result = await mock_camera_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "ezviz_cloud_account_missing" assert result["reason"] == "ezviz_cloud_account_missing"
@pytest.mark.usefixtures("ezviz_config_flow", "ezviz_test_rtsp_config_flow") @pytest.mark.usefixtures("mock_ezviz_client", "mock_test_rtsp_auth", "mock_setup_entry")
async def test_async_step_integration_discovery(hass: HomeAssistant) -> None: async def test_async_step_integration_discovery(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test discovery and confirm step.""" """Test discovery and confirm step."""
with patch("homeassistant.components.ezviz.PLATFORMS_BY_TYPE", []): mock_config_entry.add_to_hass(hass)
await init_integration(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_INTEGRATION_DISCOVERY}, data=DISCOVERY_INFO DOMAIN,
context={"source": SOURCE_INTEGRATION_DISCOVERY},
data={
ATTR_SERIAL: "C666666",
CONF_USERNAME: None,
CONF_PASSWORD: None,
CONF_IP_ADDRESS: "127.0.0.1",
},
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm" assert result["step_id"] == "confirm"
assert result["errors"] == {} assert result["errors"] == {}
with patch_async_setup_entry() as mock_setup_entry:
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
@ -208,7 +207,6 @@ async def test_async_step_integration_discovery(hass: HomeAssistant) -> None:
CONF_PASSWORD: "test-pass", CONF_PASSWORD: "test-pass",
}, },
) )
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == { assert result["data"] == {
@ -216,19 +214,19 @@ async def test_async_step_integration_discovery(hass: HomeAssistant) -> None:
CONF_TYPE: ATTR_TYPE_CAMERA, CONF_TYPE: ATTR_TYPE_CAMERA,
CONF_USERNAME: "test-user", CONF_USERNAME: "test-user",
} }
assert result["result"].unique_id == "C666666"
assert len(mock_setup_entry.mock_calls) == 1
async def test_options_flow(hass: HomeAssistant) -> None: async def test_options_flow(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test updating options.""" """Test updating options."""
with patch_async_setup_entry() as mock_setup_entry: await setup_integration(hass, mock_config_entry)
entry = await init_integration(hass)
assert entry.options[CONF_FFMPEG_ARGUMENTS] == DEFAULT_FFMPEG_ARGUMENTS assert mock_config_entry.options[CONF_FFMPEG_ARGUMENTS] == DEFAULT_FFMPEG_ARGUMENTS
assert entry.options[CONF_TIMEOUT] == DEFAULT_TIMEOUT assert mock_config_entry.options[CONF_TIMEOUT] == DEFAULT_TIMEOUT
result = await hass.config_entries.options.async_init(entry.entry_id) result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "init" assert result["step_id"] == "init"
assert result["errors"] is None assert result["errors"] is None
@ -237,19 +235,82 @@ async def test_options_flow(hass: HomeAssistant) -> None:
result["flow_id"], result["flow_id"],
user_input={CONF_FFMPEG_ARGUMENTS: "/H.264", CONF_TIMEOUT: 25}, user_input={CONF_FFMPEG_ARGUMENTS: "/H.264", CONF_TIMEOUT: 25},
) )
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"][CONF_FFMPEG_ARGUMENTS] == "/H.264" assert result["data"][CONF_FFMPEG_ARGUMENTS] == "/H.264"
assert result["data"][CONF_TIMEOUT] == 25 assert result["data"][CONF_TIMEOUT] == 25
@pytest.mark.parametrize(
("exception", "error"),
[
(InvalidURL, "invalid_host"),
(InvalidHost, "cannot_connect"),
(EzvizAuthVerificationCode, "mfa_required"),
(PyEzvizError, "invalid_auth"),
],
)
async def test_user_flow_errors(
hass: HomeAssistant,
mock_ezviz_client: AsyncMock,
mock_setup_entry: AsyncMock,
exception: Exception,
error: str,
) -> None:
"""Test the full flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
mock_ezviz_client.login.side_effect = exception
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
CONF_URL: "apiieu.ezvizlife.com",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": error}
mock_ezviz_client.login.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
CONF_URL: "apiieu.ezvizlife.com",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "test-username"
assert result["data"] == {
CONF_SESSION_ID: "fake_token",
CONF_RFSESSION_ID: "fake_rf_token",
CONF_URL: "apiieu.ezvizlife.com",
CONF_TYPE: ATTR_TYPE_CLOUD,
}
assert result["result"].unique_id == "test-username"
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
async def test_user_form_exception( @pytest.mark.usefixtures("mock_setup_entry")
hass: HomeAssistant, ezviz_config_flow: MagicMock async def test_user_flow_unknown_exception(
hass: HomeAssistant, mock_ezviz_client: AsyncMock
) -> None: ) -> None:
"""Test we handle exception on user form.""" """Test the full flow."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
@ -257,223 +318,53 @@ async def test_user_form_exception(
assert result["step_id"] == "user" assert result["step_id"] == "user"
assert result["errors"] == {} assert result["errors"] == {}
ezviz_config_flow.side_effect = PyEzvizError mock_ezviz_client.login.side_effect = Exception
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT_VALIDATE, {
) CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
assert result["type"] is FlowResultType.FORM CONF_URL: "apiieu.ezvizlife.com",
assert result["step_id"] == "user" },
assert result["errors"] == {"base": "invalid_auth"}
ezviz_config_flow.side_effect = InvalidURL
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
USER_INPUT_VALIDATE,
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "invalid_host"}
ezviz_config_flow.side_effect = EzvizAuthVerificationCode
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
USER_INPUT_VALIDATE,
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "mfa_required"}
ezviz_config_flow.side_effect = HTTPError
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
USER_INPUT_VALIDATE,
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "invalid_auth"}
ezviz_config_flow.side_effect = Exception
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
USER_INPUT_VALIDATE,
) )
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unknown" assert result["reason"] == "unknown"
async def test_discover_exception_step1( @pytest.mark.parametrize(
("exception", "error"),
[
(InvalidURL, "invalid_host"),
(InvalidHost, "cannot_connect"),
(EzvizAuthVerificationCode, "mfa_required"),
(PyEzvizError, "invalid_auth"),
],
)
async def test_user_custom_url_errors(
hass: HomeAssistant, hass: HomeAssistant,
ezviz_config_flow: MagicMock, mock_ezviz_client: AsyncMock,
mock_setup_entry: AsyncMock,
exception: Exception,
error: str,
) -> None: ) -> None:
"""Test we handle unexpected exception on discovery.""" """Test the full flow."""
with patch("homeassistant.components.ezviz.PLATFORMS_BY_TYPE", []):
await init_integration(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_INTEGRATION_DISCOVERY},
data={ATTR_SERIAL: "C66666", CONF_IP_ADDRESS: "test-ip"},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm"
assert result["errors"] == {}
# Test Step 1
ezviz_config_flow.side_effect = PyEzvizError
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-user",
CONF_PASSWORD: "test-pass",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm"
assert result["errors"] == {"base": "invalid_auth"}
ezviz_config_flow.side_effect = InvalidURL
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-user",
CONF_PASSWORD: "test-pass",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm"
assert result["errors"] == {"base": "invalid_host"}
ezviz_config_flow.side_effect = HTTPError
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-user",
CONF_PASSWORD: "test-pass",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm"
assert result["errors"] == {"base": "invalid_auth"}
ezviz_config_flow.side_effect = EzvizAuthVerificationCode
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-user",
CONF_PASSWORD: "test-pass",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm"
assert result["errors"] == {"base": "mfa_required"}
ezviz_config_flow.side_effect = Exception
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-user",
CONF_PASSWORD: "test-pass",
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unknown"
@pytest.mark.usefixtures("ezviz_config_flow")
async def test_discover_exception_step3(
hass: HomeAssistant, ezviz_test_rtsp_config_flow: MagicMock
) -> None:
"""Test we handle unexpected exception on discovery."""
with patch("homeassistant.components.ezviz.PLATFORMS_BY_TYPE", []):
await init_integration(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_INTEGRATION_DISCOVERY},
data={ATTR_SERIAL: "C66666", CONF_IP_ADDRESS: "test-ip"},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm"
assert result["errors"] == {}
# Test Step 3
ezviz_test_rtsp_config_flow.side_effect = AuthTestResultFailed
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-user",
CONF_PASSWORD: "test-pass",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm"
assert result["errors"] == {"base": "invalid_auth"}
ezviz_test_rtsp_config_flow.side_effect = InvalidHost
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-user",
CONF_PASSWORD: "test-pass",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm"
assert result["errors"] == {"base": "invalid_host"}
ezviz_test_rtsp_config_flow.side_effect = Exception
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-user",
CONF_PASSWORD: "test-pass",
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unknown"
async def test_user_custom_url_exception(
hass: HomeAssistant, ezviz_config_flow: MagicMock
) -> None:
"""Test we handle unexpected exception."""
ezviz_config_flow.side_effect = PyEzvizError()
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
mock_ezviz_client.login.side_effect = exception
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
CONF_USERNAME: "test-user", CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-pass", CONF_PASSWORD: "test-password",
CONF_URL: CONF_CUSTOMIZE, CONF_URL: CONF_CUSTOMIZE,
}, },
) )
@ -489,56 +380,33 @@ async def test_user_custom_url_exception(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user_custom_url" assert result["step_id"] == "user_custom_url"
assert result["errors"] == {"base": "invalid_auth"} assert result["errors"] == {"base": error}
ezviz_config_flow.side_effect = InvalidURL mock_ezviz_client.login.side_effect = None
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{CONF_URL: "test-user"}, {CONF_URL: "test-user"},
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["step_id"] == "user_custom_url" assert result["title"] == "test-username"
assert result["errors"] == {"base": "invalid_host"} assert result["data"] == {
CONF_SESSION_ID: "fake_token",
CONF_RFSESSION_ID: "fake_rf_token",
CONF_URL: "apiieu.ezvizlife.com",
CONF_TYPE: ATTR_TYPE_CLOUD,
}
assert result["result"].unique_id == "test-username"
ezviz_config_flow.side_effect = HTTPError assert len(mock_setup_entry.mock_calls) == 1
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_URL: "test-user"},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user_custom_url"
assert result["errors"] == {"base": "invalid_auth"}
ezviz_config_flow.side_effect = EzvizAuthVerificationCode
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_URL: "test-user"},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user_custom_url"
assert result["errors"] == {"base": "mfa_required"}
ezviz_config_flow.side_effect = Exception
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_URL: "test-user"},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unknown"
async def test_async_step_reauth_exception( @pytest.mark.usefixtures("mock_setup_entry")
hass: HomeAssistant, ezviz_config_flow: MagicMock async def test_user_custom_url_unknown_exception(
hass: HomeAssistant, mock_ezviz_client: AsyncMock
) -> None: ) -> None:
"""Test the reauth step exceptions.""" """Test the full flow."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
@ -547,26 +415,210 @@ async def test_async_step_reauth_exception(
assert result["step_id"] == "user" assert result["step_id"] == "user"
assert result["errors"] == {} assert result["errors"] == {}
with patch_async_setup_entry() as mock_setup_entry: mock_ezviz_client.login.side_effect = Exception
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT_VALIDATE, {
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
CONF_URL: CONF_CUSTOMIZE,
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user_custom_url"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_URL: "test-user"},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unknown"
async def test_already_configured(
hass: HomeAssistant,
mock_ezviz_client: AsyncMock,
mock_setup_entry: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the flow when the account is already configured."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured_account"
async def test_async_step_integration_discovery_duplicate(
hass: HomeAssistant,
mock_ezviz_client: AsyncMock,
mock_test_rtsp_auth: AsyncMock,
mock_setup_entry: AsyncMock,
mock_config_entry: MockConfigEntry,
mock_camera_config_entry: MockConfigEntry,
) -> None:
"""Test discovery and confirm step."""
mock_config_entry.add_to_hass(hass)
mock_camera_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_INTEGRATION_DISCOVERY},
data={
ATTR_SERIAL: "C666666",
CONF_USERNAME: None,
CONF_PASSWORD: None,
CONF_IP_ADDRESS: "127.0.0.1",
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
@pytest.mark.usefixtures("mock_setup_entry")
@pytest.mark.parametrize(
("exception", "error"),
[
(InvalidURL, "invalid_host"),
(InvalidHost, "invalid_host"),
(EzvizAuthVerificationCode, "mfa_required"),
(PyEzvizError, "invalid_auth"),
],
)
async def test_camera_errors(
hass: HomeAssistant,
mock_ezviz_client: AsyncMock,
mock_test_rtsp_auth: AsyncMock,
mock_config_entry: MockConfigEntry,
exception: Exception,
error: str,
) -> None:
"""Test the camera flow with errors."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_INTEGRATION_DISCOVERY},
data={
ATTR_SERIAL: "C666666",
CONF_USERNAME: None,
CONF_PASSWORD: None,
CONF_IP_ADDRESS: "127.0.0.1",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm"
assert result["errors"] == {}
mock_ezviz_client.login.side_effect = exception
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm"
assert result["errors"] == {"base": error}
mock_ezviz_client.login.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
},
) )
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "test-username" assert result["title"] == "C666666"
assert result["data"] == {**API_LOGIN_RETURN_VALIDATE} assert result["data"] == {
CONF_TYPE: ATTR_TYPE_CAMERA,
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
}
assert result["result"].unique_id == "C666666"
assert len(mock_setup_entry.mock_calls) == 1
new_entry = hass.config_entries.async_entries(DOMAIN)[0] @pytest.mark.usefixtures("mock_setup_entry")
result = await start_reauth_flow(hass, new_entry) async def test_camera_unknown_error(
hass: HomeAssistant,
mock_ezviz_client: AsyncMock,
mock_test_rtsp_auth: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the camera flow with errors."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_INTEGRATION_DISCOVERY},
data={
ATTR_SERIAL: "C666666",
CONF_USERNAME: None,
CONF_PASSWORD: None,
CONF_IP_ADDRESS: "127.0.0.1",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm"
assert result["errors"] == {}
mock_ezviz_client.login.side_effect = Exception
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unknown"
@pytest.mark.usefixtures("mock_setup_entry")
@pytest.mark.parametrize(
("exception", "error"),
[
(InvalidURL, "invalid_host"),
(InvalidHost, "invalid_host"),
(EzvizAuthVerificationCode, "mfa_required"),
(PyEzvizError, "invalid_auth"),
],
)
async def test_reauth_errors(
hass: HomeAssistant,
mock_ezviz_client: AsyncMock,
mock_config_entry: MockConfigEntry,
exception: Exception,
error: str,
) -> None:
"""Test the reauth step."""
mock_config_entry.add_to_hass(hass)
result = await mock_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {} assert result["errors"] == {}
ezviz_config_flow.side_effect = InvalidURL() mock_ezviz_client.login.side_effect = exception
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
@ -574,13 +626,12 @@ async def test_async_step_reauth_exception(
CONF_PASSWORD: "test-password", CONF_PASSWORD: "test-password",
}, },
) )
await hass.async_block_till_done()
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {"base": "invalid_host"} assert result["errors"] == {"base": error}
mock_ezviz_client.login.side_effect = None
ezviz_config_flow.side_effect = InvalidHost()
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
@ -588,49 +639,33 @@ async def test_async_step_reauth_exception(
CONF_PASSWORD: "test-password", CONF_PASSWORD: "test-password",
}, },
) )
await hass.async_block_till_done()
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {"base": "invalid_host"}
ezviz_config_flow.side_effect = EzvizAuthVerificationCode()
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {"base": "mfa_required"}
ezviz_config_flow.side_effect = PyEzvizError()
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {"base": "invalid_auth"}
ezviz_config_flow.side_effect = Exception()
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
@pytest.mark.usefixtures("mock_setup_entry")
async def test_reauth_unknown_exception(
hass: HomeAssistant,
mock_ezviz_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the reauth step."""
mock_config_entry.add_to_hass(hass)
result = await mock_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {}
mock_ezviz_client.login.side_effect = Exception
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
},
)
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unknown" assert result["reason"] == "unknown"