1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-17 07:34:07 +01:00
Files
core/tests/components/squeezebox/test_config_flow.py
2026-03-16 13:50:33 +01:00

541 lines
18 KiB
Python

"""Test the Squeezebox config flow."""
from http import HTTPStatus
from typing import Any
from unittest.mock import AsyncMock, MagicMock
import pytest
from homeassistant import config_entries
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.components.squeezebox.const import (
CONF_BROWSE_LIMIT,
CONF_SERVER_LIST,
CONF_VOLUME_STEP,
DOMAIN,
)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import entity_registry as er
from .conftest import SERVER_UUIDS
from tests.common import MockConfigEntry
# Use the same UUIDs defined in the conftest
TEST_UUID = SERVER_UUIDS[0]
async def test_manual_setup(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_server: AsyncMock
) -> None:
"""Test we can finish a manual setup successfully."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.MENU
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"next_step_id": "edit"}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "edit"
mock_server.async_query.return_value = {"uuid": TEST_UUID}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "1.2.3.4", CONF_PORT: 9000},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["result"].unique_id == TEST_UUID
assert result["title"] == "1.2.3.4"
assert result["data"][CONF_HOST] == "1.2.3.4"
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize(
("query_return", "http_status", "expected_error"),
[
(False, HTTPStatus.UNAUTHORIZED, "invalid_auth"),
(False, HTTPStatus.NOT_FOUND, "cannot_connect"),
({"no_uuid": True}, HTTPStatus.OK, "missing_uuid"),
],
)
async def test_manual_setup_data_errors(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_server: AsyncMock,
query_return: Any,
http_status: HTTPStatus,
expected_error: str,
) -> None:
"""Test data-driven error states during manual setup and recovery."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"next_step_id": "edit"}
)
mock_server.async_query.return_value = query_return
mock_server.http_status = http_status
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: "1.2.3.4", CONF_PORT: 9000}
)
assert result["type"] is FlowResultType.FORM
assert result["errors"]["base"] == expected_error
mock_server.async_query.return_value = {"uuid": TEST_UUID}
mock_server.http_status = HTTPStatus.OK
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_HOST: "1.2.3.4",
CONF_PORT: 9000,
CONF_USERNAME: "admin",
CONF_PASSWORD: "password",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["result"].unique_id == TEST_UUID
assert len(mock_setup_entry.mock_calls) == 1
async def test_manual_setup_exception_error(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_server: AsyncMock,
) -> None:
"""Test exception error state during manual setup and recovery."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"next_step_id": "edit"}
)
mock_server.async_query.side_effect = Exception("Connection fail")
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: "1.2.3.4", CONF_PORT: 9000}
)
assert result["type"] is FlowResultType.FORM
assert result["errors"]["base"] == "unknown"
mock_server.async_query.side_effect = None
mock_server.async_query.return_value = {"uuid": TEST_UUID}
mock_server.http_status = HTTPStatus.OK
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_HOST: "1.2.3.4",
CONF_PORT: 9000,
CONF_USERNAME: "admin",
CONF_PASSWORD: "password",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["result"].unique_id == TEST_UUID
assert len(mock_setup_entry.mock_calls) == 1
async def test_manual_setup_recovery(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_server: AsyncMock
) -> None:
"""Test manual setup error recovery."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"next_step_id": "edit"}
)
mock_server.async_query.return_value = False
mock_server.http_status = HTTPStatus.UNAUTHORIZED
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "1.2.3.4", CONF_PORT: 9000},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "edit"
assert result["errors"]["base"] == "invalid_auth"
mock_server.async_query.return_value = {"uuid": TEST_UUID}
mock_server.http_status = HTTPStatus.OK
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_HOST: "1.2.3.4",
CONF_PORT: 9000,
CONF_USERNAME: "admin",
CONF_PASSWORD: "correct_password",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["result"].unique_id == TEST_UUID
assert result["title"] == "1.2.3.4"
assert result["data"][CONF_HOST] == "1.2.3.4"
assert len(mock_setup_entry.mock_calls) == 1
async def test_duplicate_setup(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_server: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test abort if setting up an already configured server."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"next_step_id": "edit"}
)
mock_server.async_query.return_value = {"uuid": TEST_UUID}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "1.2.3.4", CONF_PORT: 9000},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_discovery_flow_success(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_server: AsyncMock,
mock_discover: MagicMock,
) -> None:
"""Test discovery flow where default connect succeeds immediately."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"next_step_id": "start_discovery"}
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["step_id"] == "start_discovery"
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "choose_server"
mock_server.async_query.return_value = {"uuid": TEST_UUID}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_SERVER_LIST: "1.1.1.1"}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["result"].unique_id == TEST_UUID
assert result["title"] == "1.1.1.1"
assert len(mock_setup_entry.mock_calls) == 1
async def test_discovery_flow_edit_discovered_success(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_server: AsyncMock,
mock_discover: MagicMock,
) -> None:
"""Test the successful outcome of the edit_discovered step."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"next_step_id": "start_discovery"}
)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_configure(result["flow_id"])
mock_server.http_status = HTTPStatus.UNAUTHORIZED
mock_server.async_query.return_value = False
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_SERVER_LIST: "1.1.1.1"}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "edit_discovered"
mock_server.http_status = HTTPStatus.OK
mock_server.async_query.side_effect = [False, {"uuid": TEST_UUID}]
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: "admin", CONF_PASSWORD: "password"}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "1.1.1.1"
assert result["data"][CONF_HOST] == "1.1.1.1"
assert result["data"][CONF_USERNAME] == "admin"
assert result["data"][CONF_PASSWORD] == "password"
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize(
("side_effect", "http_status", "expected_error"),
[
(False, HTTPStatus.UNAUTHORIZED, "invalid_auth"),
(False, HTTPStatus.NOT_FOUND, "cannot_connect"),
({"no_uuid": True}, HTTPStatus.OK, "missing_uuid"),
(Exception("Test error"), None, "unknown"),
],
)
async def test_discovery_flow_edit_discovered_errors(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_server: AsyncMock,
mock_discover: MagicMock,
side_effect: Any,
http_status: HTTPStatus | None,
expected_error: str,
) -> None:
"""Test all error outcomes of the edit_discovered step."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"next_step_id": "start_discovery"}
)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_configure(result["flow_id"])
mock_server.http_status = HTTPStatus.UNAUTHORIZED
mock_server.async_query.return_value = False
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_SERVER_LIST: "1.1.1.1"}
)
mock_server.http_status = http_status
mock_server.async_query.side_effect = [False, side_effect]
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: "admin", CONF_PASSWORD: "password"}
)
assert result["type"] is FlowResultType.FORM
assert result["errors"]["base"] == expected_error
async def test_discovery_flow_failed(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_discover: MagicMock
) -> None:
"""Test discovery flow when no servers are found."""
async def _failed_discover(callback: Any) -> list:
return []
mock_discover.side_effect = _failed_discover
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"next_step_id": "start_discovery"}
)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.MENU
assert result["step_id"] == "discovery_failed"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"next_step_id": "edit"}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "edit"
async def test_discovery_ignores_existing(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_config_entry: MockConfigEntry,
mock_discover: MagicMock,
) -> None:
"""Test discovery properly ignores a server that's already configured."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"next_step_id": "start_discovery"}
)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.MENU
assert result["step_id"] == "discovery_failed"
async def test_integration_discovery(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_server: AsyncMock
) -> None:
"""Test integration discovery flow with a provided UUID."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
data={CONF_HOST: "1.2.3.4", CONF_PORT: 9000, "uuid": TEST_UUID},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "edit_integration_discovered"
mock_server.async_query.return_value = {"uuid": TEST_UUID}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: "admin", CONF_PASSWORD: "password"}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["result"].unique_id == TEST_UUID
assert result["title"] == "1.2.3.4"
assert len(mock_setup_entry.mock_calls) == 1
async def test_integration_discovery_no_uuid(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_server: AsyncMock
) -> None:
"""Test integration discovery flow without a UUID."""
mock_server.async_query.return_value = {"uuid": TEST_UUID}
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
data={CONF_HOST: "1.2.3.4", CONF_PORT: 9000},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "edit_integration_discovered"
async def test_integration_discovery_no_uuid_fails(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_server: AsyncMock
) -> None:
"""Test integration discovery flow routes to form when connection fails."""
mock_server.async_query.return_value = False
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
data={CONF_HOST: "1.2.3.4", CONF_PORT: 9000},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "edit_integration_discovered"
async def test_integration_discovery_edit_recovery(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_server: AsyncMock
) -> None:
"""Test editing an integration discovery returns errors and can recover."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
data={CONF_HOST: "1.2.3.4", CONF_PORT: 9000, "uuid": TEST_UUID},
)
mock_server.async_query.return_value = False
mock_server.http_status = HTTPStatus.UNAUTHORIZED
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: "admin", CONF_PASSWORD: "wrongpassword"}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "edit_integration_discovered"
assert result["errors"]["base"] == "invalid_auth"
mock_server.async_query.return_value = {"uuid": TEST_UUID}
mock_server.http_status = HTTPStatus.OK
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: "admin", CONF_PASSWORD: "correctpassword"}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["result"].unique_id == TEST_UUID
assert result["title"] == "1.2.3.4"
assert len(mock_setup_entry.mock_calls) == 1
async def test_dhcp_unknown_player(
hass: HomeAssistant, dhcp_info: dict[str, Any]
) -> None:
"""Test DHCP discovery of an unconfigured player routes to user setup."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp_info,
)
assert result["type"] is FlowResultType.MENU
assert result["step_id"] == "user"
async def test_dhcp_known_player(
hass: HomeAssistant, dhcp_info: dict[str, Any], mock_config_entry: MockConfigEntry
) -> None:
"""Test DHCP discovery aborts if player is already registered."""
mock_config_entry.add_to_hass(hass)
entity_registry = er.async_get(hass)
entity_registry.async_get_or_create(
MP_DOMAIN, DOMAIN, "aa:bb:cc:dd:ee:ff", config_entry=mock_config_entry
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp_info,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_options_flow(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test the options flow."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={CONF_BROWSE_LIMIT: 500, CONF_VOLUME_STEP: 5},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert mock_config_entry.options == {
CONF_BROWSE_LIMIT: 500,
CONF_VOLUME_STEP: 5,
}