diff --git a/tests/components/vilfo/conftest.py b/tests/components/vilfo/conftest.py new file mode 100644 index 00000000000..75ed352c839 --- /dev/null +++ b/tests/components/vilfo/conftest.py @@ -0,0 +1,61 @@ +"""Vilfo tests conftest.""" + +from collections.abc import Generator +from unittest.mock import AsyncMock, patch + +import pytest + +from homeassistant.components.vilfo import DOMAIN +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST + +from tests.common import MockConfigEntry + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock, None, None]: + """Override async_setup_entry.""" + with patch( + "homeassistant.components.vilfo.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + yield mock_setup_entry + + +@pytest.fixture +def mock_vilfo_client() -> Generator[AsyncMock, None, None]: + """Mock a Vilfo client.""" + with patch( + "homeassistant.components.vilfo.config_flow.VilfoClient", + autospec=True, + ) as mock_client: + client = mock_client.return_value + client.get_board_information.return_value = None + client.ping.return_value = None + client.resolve_firmware_version.return_value = "1.1.0" + client.resolve_mac_address.return_value = "FF-00-00-00-00-00" + client.mac = "FF-00-00-00-00-00" + yield client + + +@pytest.fixture +def mock_is_valid_host() -> Generator[AsyncMock, None, None]: + """Mock is_valid_host.""" + with patch( + "homeassistant.components.vilfo.config_flow.is_host_valid", + return_value=True, + ) as mock_is_valid_host: + yield mock_is_valid_host + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Mock a config entry.""" + return MockConfigEntry( + domain=DOMAIN, + title="testadmin.vilfo.com", + unique_id="FF-00-00-00-00-00", + data={ + CONF_HOST: "testadmin.vilfo.com", + CONF_ACCESS_TOKEN: "test-token", + }, + ) diff --git a/tests/components/vilfo/test_config_flow.py b/tests/components/vilfo/test_config_flow.py index 51c2698e241..c4fdb2fe22c 100644 --- a/tests/components/vilfo/test_config_flow.py +++ b/tests/components/vilfo/test_config_flow.py @@ -1,219 +1,191 @@ """Test the Vilfo Router config flow.""" -from unittest.mock import Mock, patch +from typing import Any +from unittest.mock import AsyncMock -import vilfo +import pytest +from vilfo.exceptions import AuthenticationException, VilfoException -from homeassistant import config_entries -from homeassistant.components.vilfo import config_flow from homeassistant.components.vilfo.const import DOMAIN -from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_ID, CONF_MAC +from homeassistant.config_entries import SOURCE_USER +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType +from tests.common import MockConfigEntry -async def test_form(hass: HomeAssistant) -> None: - """Test we get the form.""" - mock_mac = "FF-00-00-00-00-00" - firmware_version = "1.1.0" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] is FlowResultType.FORM - assert result["errors"] == {} - - with ( - patch("vilfo.Client.ping", return_value=None), - patch("vilfo.Client.get_board_information", return_value=None), - patch("vilfo.Client.resolve_firmware_version", return_value=firmware_version), - patch("vilfo.Client.resolve_mac_address", return_value=mock_mac), - patch("homeassistant.components.vilfo.async_setup_entry") as mock_setup_entry, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], +@pytest.mark.parametrize( + ("user_input", "expected_unique_id", "mac"), + [ + ( {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, - ) - await hass.async_block_till_done() + "testadmin.vilfo.com", + None, + ), + ( + {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, + "FF-00-00-00-00-00", + "FF-00-00-00-00-00", + ), + ( + {CONF_HOST: "192.168.0.1", CONF_ACCESS_TOKEN: "test-token"}, + "FF-00-00-00-00-00", + "FF-00-00-00-00-00", + ), + ( + {CONF_HOST: "2001:db8::1428:57ab", CONF_ACCESS_TOKEN: "test-token"}, + "FF-00-00-00-00-00", + "FF-00-00-00-00-00", + ), + ], +) +async def test_full_flow( + hass: HomeAssistant, + mock_vilfo_client: AsyncMock, + mock_setup_entry: AsyncMock, + mock_is_valid_host: AsyncMock, + user_input: dict[str, Any], + expected_unique_id: str, + mac: str | None, +) -> None: + """Test we can finish a config flow.""" - assert result2["type"] is FlowResultType.CREATE_ENTRY - assert result2["title"] == "testadmin.vilfo.com" - assert result2["data"] == { - "host": "testadmin.vilfo.com", - "access_token": "test-token", - } + mock_vilfo_client.resolve_mac_address.return_value = mac + mock_vilfo_client.mac = mac + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + await hass.async_block_till_done() + assert result["type"] is FlowResultType.FORM + assert not result["errors"] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == user_input[CONF_HOST] + assert result["data"] == user_input + assert result["result"].unique_id == expected_unique_id assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_invalid_auth(hass: HomeAssistant) -> None: +async def test_form_invalid_auth( + hass: HomeAssistant, + mock_vilfo_client: AsyncMock, + mock_is_valid_host: AsyncMock, + mock_setup_entry: AsyncMock, +) -> None: """Test we handle invalid auth.""" + mock_vilfo_client.get_board_information.side_effect = AuthenticationException + mock_vilfo_client.resolve_mac_address.return_value = None + result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} + ) + await hass.async_block_till_done() + assert result["type"] is FlowResultType.FORM + assert not result["errors"] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": "invalid_auth"} + + mock_vilfo_client.get_board_information.side_effect = None + mock_vilfo_client.resolve_mac_address.return_value = "FF-00-00-00-00-00" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.CREATE_ENTRY + + +@pytest.mark.parametrize( + ("side_effect", "error"), + [(VilfoException, "cannot_connect"), (Exception, "unknown")], +) +async def test_form_exceptions( + hass: HomeAssistant, + mock_vilfo_client: AsyncMock, + mock_is_valid_host: AsyncMock, + mock_setup_entry: AsyncMock, + side_effect: Exception, + error: str, +) -> None: + """Test we handle exceptions.""" + mock_vilfo_client.ping.side_effect = side_effect + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} ) - with ( - patch("vilfo.Client.ping", return_value=None), - patch("vilfo.Client.resolve_mac_address", return_value=None), - patch( - "vilfo.Client.get_board_information", - side_effect=vilfo.exceptions.AuthenticationException, - ), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"host": "testadmin.vilfo.com", "access_token": "test-token"}, - ) - - assert result2["type"] is FlowResultType.FORM - assert result2["errors"] == {"base": "invalid_auth"} - - -async def test_form_cannot_connect(hass: HomeAssistant) -> None: - """Test we handle cannot connect error.""" - 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"], + {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, ) - with ( - patch("vilfo.Client.ping", side_effect=vilfo.exceptions.VilfoException), - patch("vilfo.Client.resolve_mac_address"), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"host": "testadmin.vilfo.com", "access_token": "test-token"}, - ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": error} - assert result2["type"] is FlowResultType.FORM - assert result2["errors"] == {"base": "cannot_connect"} + mock_vilfo_client.ping.side_effect = None - with ( - patch("vilfo.Client.ping", side_effect=vilfo.exceptions.VilfoException), - patch("vilfo.Client.resolve_mac_address"), - ): - result3 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"host": "testadmin.vilfo.com", "access_token": "test-token"}, - ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, + ) + await hass.async_block_till_done() - assert result3["type"] is FlowResultType.FORM - assert result3["errors"] == {"base": "cannot_connect"} + assert result["type"] is FlowResultType.CREATE_ENTRY -async def test_form_wrong_host(hass: HomeAssistant) -> None: +async def test_form_wrong_host( + hass: HomeAssistant, + mock_is_valid_host: AsyncMock, +) -> None: """Test we handle wrong host errors.""" + mock_is_valid_host.return_value = False result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data={"host": "this is an invalid hostname", "access_token": "test-token"}, + context={"source": SOURCE_USER}, + data={ + CONF_HOST: "this is an invalid hostname", + CONF_ACCESS_TOKEN: "test-token", + }, ) assert result["errors"] == {"host": "wrong_host"} -async def test_form_already_configured(hass: HomeAssistant) -> None: +async def test_form_already_configured( + hass: HomeAssistant, + mock_vilfo_client: AsyncMock, + mock_setup_entry: AsyncMock, + mock_config_entry: MockConfigEntry, + mock_is_valid_host: AsyncMock, +) -> None: """Test that we handle already configured exceptions appropriately.""" - first_flow_result1 = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - firmware_version = "1.1.0" - with ( - patch("vilfo.Client.ping", return_value=None), - patch( - "vilfo.Client.get_board_information", - return_value=None, - ), - patch("vilfo.Client.resolve_firmware_version", return_value=firmware_version), - patch("vilfo.Client.resolve_mac_address", return_value=None), - ): - first_flow_result2 = await hass.config_entries.flow.async_configure( - first_flow_result1["flow_id"], - {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, - ) + mock_config_entry.add_to_hass(hass) - second_flow_result1 = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with ( - patch("vilfo.Client.ping", return_value=None), - patch( - "vilfo.Client.get_board_information", - return_value=None, - ), - patch("vilfo.Client.resolve_firmware_version", return_value=firmware_version), - patch("vilfo.Client.resolve_mac_address", return_value=None), - ): - second_flow_result2 = await hass.config_entries.flow.async_configure( - second_flow_result1["flow_id"], - {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, - ) - - assert first_flow_result2["type"] is FlowResultType.CREATE_ENTRY - assert second_flow_result2["type"] is FlowResultType.ABORT - assert second_flow_result2["reason"] == "already_configured" - - -async def test_form_unexpected_exception(hass: HomeAssistant) -> None: - """Test that we handle unexpected exceptions.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) - - with patch( - "homeassistant.components.vilfo.config_flow.VilfoClient", - ) as mock_client: - mock_client.return_value.ping = Mock(side_effect=Exception) - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"host": "testadmin.vilfo.com", "access_token": "test-token"}, - ) - - assert result2["errors"] == {"base": "unknown"} - - -async def test_validate_input_returns_data(hass: HomeAssistant) -> None: - """Test we handle the MAC address being resolved or not.""" - mock_data = {"host": "testadmin.vilfo.com", "access_token": "test-token"} - mock_data_with_ip = {"host": "192.168.0.1", "access_token": "test-token"} - mock_data_with_ipv6 = {"host": "2001:db8::1428:57ab", "access_token": "test-token"} - mock_mac = "FF-00-00-00-00-00" - firmware_version = "1.1.0" - - with ( - patch("vilfo.Client.ping", return_value=None), - patch("vilfo.Client.get_board_information", return_value=None), - patch("vilfo.Client.resolve_firmware_version", return_value=firmware_version), - patch("vilfo.Client.resolve_mac_address", return_value=None), - ): - result = await config_flow.validate_input(hass, data=mock_data) - - assert result["title"] == mock_data["host"] - assert result[CONF_HOST] == mock_data["host"] - assert result[CONF_MAC] is None - assert result[CONF_ID] == mock_data["host"] - - with ( - patch("vilfo.Client.ping", return_value=None), - patch("vilfo.Client.get_board_information", return_value=None), - patch("vilfo.Client.resolve_firmware_version", return_value=firmware_version), - patch("vilfo.Client.resolve_mac_address", return_value=mock_mac), - ): - result2 = await config_flow.validate_input(hass, data=mock_data) - result3 = await config_flow.validate_input(hass, data=mock_data_with_ip) - result4 = await config_flow.validate_input(hass, data=mock_data_with_ipv6) - - assert result2["title"] == mock_data["host"] - assert result2[CONF_HOST] == mock_data["host"] - assert result2[CONF_MAC] == mock_mac - assert result2[CONF_ID] == mock_mac - - assert result3["title"] == mock_data_with_ip["host"] - assert result3[CONF_HOST] == mock_data_with_ip["host"] - assert result3[CONF_MAC] == mock_mac - assert result3[CONF_ID] == mock_mac - - assert result4["title"] == mock_data_with_ipv6["host"] - assert result4[CONF_HOST] == mock_data_with_ipv6["host"] - assert result4[CONF_MAC] == mock_mac - assert result4[CONF_ID] == mock_mac + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, + ) + await hass.async_block_till_done() + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured"