core/tests/components/letpot/test_config_flow.py

318 lines
11 KiB
Python

"""Test the LetPot config flow."""
import dataclasses
from typing import Any
from unittest.mock import AsyncMock, patch
from letpot.exceptions import LetPotAuthenticationException, LetPotConnectionException
import pytest
from homeassistant.components.letpot.const import (
CONF_ACCESS_TOKEN_EXPIRES,
CONF_REFRESH_TOKEN,
CONF_REFRESH_TOKEN_EXPIRES,
CONF_USER_ID,
DOMAIN,
)
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_EMAIL, CONF_PASSWORD
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from . import AUTHENTICATION
from tests.common import MockConfigEntry
def _assert_result_success(result: Any) -> None:
"""Assert successful end of flow result, creating an entry."""
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == AUTHENTICATION.email
assert result["data"] == {
CONF_ACCESS_TOKEN: AUTHENTICATION.access_token,
CONF_ACCESS_TOKEN_EXPIRES: AUTHENTICATION.access_token_expires,
CONF_REFRESH_TOKEN: AUTHENTICATION.refresh_token,
CONF_REFRESH_TOKEN_EXPIRES: AUTHENTICATION.refresh_token_expires,
CONF_USER_ID: AUTHENTICATION.user_id,
CONF_EMAIL: AUTHENTICATION.email,
}
assert result["result"].unique_id == AUTHENTICATION.user_id
async def test_full_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test full flow with success."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {}
with patch(
"homeassistant.components.letpot.config_flow.LetPotClient.login",
return_value=AUTHENTICATION,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_EMAIL: "email@example.com",
CONF_PASSWORD: "test-password",
},
)
await hass.async_block_till_done()
_assert_result_success(result)
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize(
("exception", "error"),
[
(LetPotAuthenticationException, "invalid_auth"),
(LetPotConnectionException, "cannot_connect"),
(Exception, "unknown"),
],
)
async def test_flow_exceptions(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
exception: Exception,
error: str,
) -> None:
"""Test flow with exception during login and recovery."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
with patch(
"homeassistant.components.letpot.config_flow.LetPotClient.login",
side_effect=exception,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_EMAIL: "email@example.com",
CONF_PASSWORD: "test-password",
},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}
# Retry to show recovery.
with patch(
"homeassistant.components.letpot.config_flow.LetPotClient.login",
return_value=AUTHENTICATION,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_EMAIL: "email@example.com",
CONF_PASSWORD: "test-password",
},
)
await hass.async_block_till_done()
_assert_result_success(result)
assert len(mock_setup_entry.mock_calls) == 1
async def test_flow_duplicate(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test flow aborts when trying to add a previously added account."""
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.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
with patch(
"homeassistant.components.letpot.config_flow.LetPotClient.login",
return_value=AUTHENTICATION,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_EMAIL: "email@example.com",
CONF_PASSWORD: "test-password",
},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
assert len(mock_setup_entry.mock_calls) == 0
async def test_reauth_flow(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test reauth flow with success."""
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"
updated_auth = dataclasses.replace(
AUTHENTICATION,
access_token="new_access_token",
refresh_token="new_refresh_token",
)
with patch(
"homeassistant.components.letpot.config_flow.LetPotClient.login",
return_value=updated_auth,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert mock_config_entry.data == {
CONF_ACCESS_TOKEN: "new_access_token",
CONF_ACCESS_TOKEN_EXPIRES: AUTHENTICATION.access_token_expires,
CONF_REFRESH_TOKEN: "new_refresh_token",
CONF_REFRESH_TOKEN_EXPIRES: AUTHENTICATION.refresh_token_expires,
CONF_USER_ID: AUTHENTICATION.user_id,
CONF_EMAIL: AUTHENTICATION.email,
}
assert len(hass.config_entries.async_entries()) == 1
@pytest.mark.parametrize(
("exception", "error"),
[
(LetPotAuthenticationException, "invalid_auth"),
(LetPotConnectionException, "cannot_connect"),
(Exception, "unknown"),
],
)
async def test_reauth_exceptions(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
exception: Exception,
error: str,
) -> None:
"""Test reauth flow with exception during login and recovery."""
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"
with patch(
"homeassistant.components.letpot.config_flow.LetPotClient.login",
side_effect=exception,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}
# Retry to show recovery.
updated_auth = dataclasses.replace(
AUTHENTICATION,
access_token="new_access_token",
refresh_token="new_refresh_token",
)
with patch(
"homeassistant.components.letpot.config_flow.LetPotClient.login",
return_value=updated_auth,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert mock_config_entry.data == {
CONF_ACCESS_TOKEN: "new_access_token",
CONF_ACCESS_TOKEN_EXPIRES: AUTHENTICATION.access_token_expires,
CONF_REFRESH_TOKEN: "new_refresh_token",
CONF_REFRESH_TOKEN_EXPIRES: AUTHENTICATION.refresh_token_expires,
CONF_USER_ID: AUTHENTICATION.user_id,
CONF_EMAIL: AUTHENTICATION.email,
}
assert len(hass.config_entries.async_entries()) == 1
async def test_reauth_different_user_id_new(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test reauth flow with different, new user ID updating the existing entry."""
mock_config_entry.add_to_hass(hass)
config_entries = hass.config_entries.async_entries()
assert len(config_entries) == 1
assert config_entries[0].unique_id == AUTHENTICATION.user_id
result = await mock_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
updated_auth = dataclasses.replace(AUTHENTICATION, user_id="new_user_id")
with patch(
"homeassistant.components.letpot.config_flow.LetPotClient.login",
return_value=updated_auth,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert mock_config_entry.data == {
CONF_ACCESS_TOKEN: AUTHENTICATION.access_token,
CONF_ACCESS_TOKEN_EXPIRES: AUTHENTICATION.access_token_expires,
CONF_REFRESH_TOKEN: AUTHENTICATION.refresh_token,
CONF_REFRESH_TOKEN_EXPIRES: AUTHENTICATION.refresh_token_expires,
CONF_USER_ID: "new_user_id",
CONF_EMAIL: AUTHENTICATION.email,
}
config_entries = hass.config_entries.async_entries()
assert len(config_entries) == 1
assert config_entries[0].unique_id == "new_user_id"
async def test_reauth_different_user_id_existing(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test reauth flow with different, existing user ID aborting."""
mock_config_entry.add_to_hass(hass)
mock_other = MockConfigEntry(
domain=DOMAIN, title="email2@example.com", data={}, unique_id="other_user_id"
)
mock_other.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"
updated_auth = dataclasses.replace(AUTHENTICATION, user_id="other_user_id")
with patch(
"homeassistant.components.letpot.config_flow.LetPotClient.login",
return_value=updated_auth,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
assert len(hass.config_entries.async_entries()) == 2