Improve Meater config flow tests (#146951)

This commit is contained in:
Joost Lekkerkerker 2025-06-16 15:40:43 +02:00 committed by GitHub
parent d4686a3cce
commit 664441eaec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 135 additions and 106 deletions

View File

@ -0,0 +1,49 @@
"""Meater tests configuration."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant.components.meater.const import DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from tests.common import MockConfigEntry
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.meater.async_setup_entry",
return_value=True,
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture
def mock_meater_client() -> Generator[AsyncMock]:
"""Mock a Meater client."""
with (
patch(
"homeassistant.components.meater.coordinator.MeaterApi",
autospec=True,
) as mock_client,
patch(
"homeassistant.components.meater.config_flow.MeaterApi",
new=mock_client,
),
):
client = mock_client.return_value
yield client
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Mock a config entry."""
return MockConfigEntry(
domain=DOMAIN,
title="Meater",
data={CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"},
unique_id="user@host.com",
)

View File

@ -1,12 +1,12 @@
"""Define tests for the Meater config flow.""" """Define tests for the Meater config flow."""
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock
from meater import AuthenticationError, ServiceUnavailableError from meater import AuthenticationError, ServiceUnavailableError
import pytest import pytest
from homeassistant import config_entries
from homeassistant.components.meater import DOMAIN from homeassistant.components.meater import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
@ -14,134 +14,114 @@ from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@pytest.fixture async def test_user_flow(
def mock_client(): hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_meater_client: AsyncMock
"""Define a fixture for authentication coroutine.""" ) -> None:
return AsyncMock(return_value=None)
@pytest.fixture
def mock_meater(mock_client):
"""Mock the meater library."""
with patch(
"homeassistant.components.meater.coordinator.MeaterApi.authenticate"
) as mock_:
mock_.side_effect = mock_client
yield mock_
async def test_duplicate_error(hass: HomeAssistant) -> None:
"""Test that errors are shown when duplicates are added."""
conf = {CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"}
MockConfigEntry(domain=DOMAIN, unique_id="user@host.com", data=conf).add_to_hass(
hass
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=conf
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
@pytest.mark.parametrize("mock_client", [AsyncMock(side_effect=Exception)])
async def test_unknown_auth_error(hass: HomeAssistant, mock_meater) -> None:
"""Test that an invalid API/App Key throws an error."""
conf = {CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"}
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=conf
)
assert result["errors"] == {"base": "unknown_auth_error"}
@pytest.mark.parametrize("mock_client", [AsyncMock(side_effect=AuthenticationError)])
async def test_invalid_credentials(hass: HomeAssistant, mock_meater) -> None:
"""Test that an invalid API/App Key throws an error."""
conf = {CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"}
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=conf
)
assert result["errors"] == {"base": "invalid_auth"}
@pytest.mark.parametrize(
"mock_client", [AsyncMock(side_effect=ServiceUnavailableError)]
)
async def test_service_unavailable(hass: HomeAssistant, mock_meater) -> None:
"""Test that an invalid API/App Key throws an error."""
conf = {CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"}
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=conf
)
assert result["errors"] == {"base": "service_unavailable_error"}
async def test_user_flow(hass: HomeAssistant, mock_meater) -> None:
"""Test that the user flow works.""" """Test that the user flow works."""
conf = {CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"}
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=None DOMAIN, context={"source": SOURCE_USER}
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
with patch( result = await hass.config_entries.flow.async_configure(
"homeassistant.components.meater.async_setup_entry", result["flow_id"],
return_value=True, {CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"},
) as mock_setup_entry: )
result = await hass.config_entries.flow.async_configure(result["flow_id"], conf)
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"] == {
CONF_USERNAME: "user@host.com", CONF_USERNAME: "user@host.com",
CONF_PASSWORD: "password123", CONF_PASSWORD: "password123",
} }
assert result["result"].unique_id == "user@host.com"
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
assert config_entry.data == {
CONF_USERNAME: "user@host.com",
CONF_PASSWORD: "password123",
}
@pytest.mark.parametrize(
async def test_reauth_flow(hass: HomeAssistant, mock_meater) -> None: ("exception", "error"),
"""Test that the reauth flow works.""" [
data = { (AuthenticationError, "invalid_auth"),
CONF_USERNAME: "user@host.com", (ServiceUnavailableError, "service_unavailable_error"),
CONF_PASSWORD: "password123", (Exception, "unknown_auth_error"),
} ],
mock_config = MockConfigEntry( )
domain=DOMAIN, async def test_user_flow_exceptions(
unique_id="user@host.com", hass: HomeAssistant,
data=data, mock_setup_entry: AsyncMock,
mock_meater_client: AsyncMock,
exception: Exception,
error: str,
) -> None:
"""Test that an invalid API/App Key throws an error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
) )
mock_config.add_to_hass(hass)
result = await mock_config.start_reauth_flow(hass) mock_meater_client.authenticate.side_effect = exception
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": error}
mock_meater_client.authenticate.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
async def test_duplicate_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_meater_client: AsyncMock,
) -> None:
"""Test that errors are shown when duplicates are added."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_USERNAME: "user@host.com", CONF_PASSWORD: "password123"},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_reauth_flow(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_meater_client: AsyncMock,
) -> None:
"""Test that the reauth flow works."""
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"] is None assert not result["errors"]
result2 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{"password": "passwordabc"}, {CONF_PASSWORD: "passwordabc"},
) )
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result2["reason"] == "reauth_successful" assert result["reason"] == "reauth_successful"
assert mock_config_entry.data == {
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
assert config_entry.data == {
CONF_USERNAME: "user@host.com", CONF_USERNAME: "user@host.com",
CONF_PASSWORD: "passwordabc", CONF_PASSWORD: "passwordabc",
} }