core/tests/components/melcloud/test_config_flow.py

290 lines
9.1 KiB
Python

"""Test the MELCloud config flow."""
import asyncio
from http import HTTPStatus
from unittest.mock import patch
from aiohttp import ClientError, ClientResponseError
import pymelcloud
import pytest
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.melcloud.const import DOMAIN
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
import homeassistant.helpers.issue_registry as ir
from tests.common import MockConfigEntry
@pytest.fixture
def mock_login():
"""Mock pymelcloud login."""
with patch(
"homeassistant.components.melcloud.config_flow.pymelcloud.login"
) as mock:
mock.return_value = "test-token"
yield mock
@pytest.fixture
def mock_get_devices():
"""Mock pymelcloud get_devices."""
with patch(
"homeassistant.components.melcloud.config_flow.pymelcloud.get_devices"
) as mock:
mock.return_value = {
pymelcloud.DEVICE_TYPE_ATA: [],
pymelcloud.DEVICE_TYPE_ATW: [],
}
yield mock
@pytest.fixture
def mock_request_info():
"""Mock RequestInfo to create ClientResponseErrors."""
with patch("aiohttp.RequestInfo") as mock_ri:
mock_ri.return_value.real_url.return_value = ""
yield mock_ri
async def test_form(hass: HomeAssistant, mock_login, mock_get_devices) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == "form"
assert result["errors"] is None
with patch(
"homeassistant.components.melcloud.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.melcloud.async_setup_entry", return_value=True
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"username": "test-email@test-domain.com", "password": "test-password"},
)
await hass.async_block_till_done()
assert result2["type"] == "create_entry"
assert result2["title"] == "test-email@test-domain.com"
assert result2["data"] == {
"username": "test-email@test-domain.com",
"token": "test-token",
}
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize(
("error", "reason"),
[(ClientError(), "cannot_connect"), (asyncio.TimeoutError(), "cannot_connect")],
)
async def test_form_errors(
hass: HomeAssistant, mock_login, mock_get_devices, error, reason
) -> None:
"""Test we handle cannot connect error."""
mock_login.side_effect = error
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_USER},
data={"username": "test-email@test-domain.com", "password": "test-password"},
)
assert len(mock_login.mock_calls) == 1
assert result["type"] == "abort"
assert result["reason"] == reason
@pytest.mark.parametrize(
("error", "message"),
[
(HTTPStatus.UNAUTHORIZED, "invalid_auth"),
(HTTPStatus.FORBIDDEN, "invalid_auth"),
(HTTPStatus.INTERNAL_SERVER_ERROR, "cannot_connect"),
],
)
async def test_form_response_errors(
hass: HomeAssistant, mock_login, mock_get_devices, mock_request_info, error, message
) -> None:
"""Test we handle response errors."""
mock_login.side_effect = ClientResponseError(mock_request_info(), (), status=error)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_USER},
data={"username": "test-email@test-domain.com", "password": "test-password"},
)
assert result["type"] == "abort"
assert result["reason"] == message
@pytest.mark.parametrize(
("error", "message", "issue"),
[
(
HTTPStatus.UNAUTHORIZED,
"invalid_auth",
"deprecated_yaml_import_issue_invalid_auth",
),
(
HTTPStatus.FORBIDDEN,
"invalid_auth",
"deprecated_yaml_import_issue_invalid_auth",
),
(
HTTPStatus.INTERNAL_SERVER_ERROR,
"cannot_connect",
"deprecated_yaml_import_issue_cannot_connect",
),
],
)
async def test_step_import_fails(
hass: HomeAssistant,
mock_login,
mock_get_devices,
mock_request_info,
error: Exception,
message: str,
issue: str,
) -> None:
"""Test raising issues on import."""
mock_get_devices.side_effect = ClientResponseError(
mock_request_info(), (), status=error
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={"username": "test-email@test-domain.com", "token": "test-token"},
)
assert result["type"] == data_entry_flow.FlowResultType.ABORT
assert result["reason"] == message
issue_registry = ir.async_get(hass)
assert issue_registry.async_get_issue(DOMAIN, issue)
async def test_step_import_fails_ClientError(
hass: HomeAssistant,
mock_login,
mock_get_devices,
mock_request_info,
) -> None:
"""Test raising issues on import for ClientError."""
mock_get_devices.side_effect = ClientError()
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={"username": "test-email@test-domain.com", "token": "test-token"},
)
assert result["type"] == data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "cannot_connect"
issue_registry = ir.async_get(hass)
assert issue_registry.async_get_issue(
DOMAIN, "deprecated_yaml_import_issue_cannot_connect"
)
async def test_step_import_already_exist(
hass: HomeAssistant,
mock_login,
mock_get_devices,
mock_request_info,
) -> None:
"""Test that errors are shown when duplicates are added."""
conf = {"username": "test-email@test-domain.com", "token": "test-token"}
config_entry = MockConfigEntry(
domain=DOMAIN,
data=conf,
title=conf["username"],
unique_id=conf["username"],
)
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=conf
)
assert result["type"] == data_entry_flow.FlowResultType.ABORT
assert result["reason"] == "already_configured"
issue_registry = ir.async_get(hass)
issue = issue_registry.async_get_issue(
HOMEASSISTANT_DOMAIN, "deprecated_yaml_melcloud"
)
assert issue.translation_key == "deprecated_yaml"
async def test_import_with_token(
hass: HomeAssistant, mock_login, mock_get_devices
) -> None:
"""Test successful import."""
with patch(
"homeassistant.components.melcloud.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.melcloud.async_setup_entry", return_value=True
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={"username": "test-email@test-domain.com", "token": "test-token"},
)
await hass.async_block_till_done()
assert result["type"] == "create_entry"
assert result["title"] == "test-email@test-domain.com"
assert result["data"] == {
"username": "test-email@test-domain.com",
"token": "test-token",
}
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
issue_registry = ir.async_get(hass)
issue = issue_registry.async_get_issue(
HOMEASSISTANT_DOMAIN, "deprecated_yaml_melcloud"
)
assert issue.translation_key == "deprecated_yaml"
async def test_token_refresh(hass: HomeAssistant, mock_login, mock_get_devices) -> None:
"""Re-configuration with existing username should refresh token."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={"username": "test-email@test-domain.com", "token": "test-original-token"},
unique_id="test-email@test-domain.com",
)
mock_entry.add_to_hass(hass)
with patch(
"homeassistant.components.melcloud.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.melcloud.async_setup_entry", return_value=True
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_USER},
data={
"username": "test-email@test-domain.com",
"password": "test-password",
},
)
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 0
assert len(mock_setup_entry.mock_calls) == 0
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
entry = entries[0]
assert entry.data["username"] == "test-email@test-domain.com"
assert entry.data["token"] == "test-token"