"""Tests for the MB-Secure config flow.""" from unittest.mock import patch from homeassistant.config_entries import SOURCE_HASSIO, SOURCE_REAUTH, SOURCE_USER from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType from homeassistant.helpers.service_info.hassio import HassioServiceInfo from pytest_homeassistant_custom_component.common import MockConfigEntry from custom_components.mb_secure.api import BridgeAuthenticationError from custom_components.mb_secure.const import CONF_TOKEN, DOMAIN from custom_components.mb_secure.models import BridgeInfo USER_INPUT = { CONF_HOST: "bridge.local", CONF_PORT: 8099, CONF_TOKEN: "x" * 32, } async def test_user_flow_creates_entry_with_stable_bridge_id( hass: HomeAssistant, ) -> None: """Successful validation uses the installation ID as config entry ID.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] is FlowResultType.FORM info = BridgeInfo( bridge_id="bridge-installation-1", bridge_version="0.1.0", api_version=1, connected=False, capabilities=frozenset({"snapshot", "events"}), ) with patch( "custom_components.mb_secure.api.BridgeClient.async_get_info", return_value=info, ): result = await hass.config_entries.flow.async_configure( result["flow_id"], USER_INPUT ) assert result["type"] is FlowResultType.CREATE_ENTRY assert result["data"] == USER_INPUT assert result["result"].unique_id == "bridge-installation-1" async def test_user_flow_reports_invalid_token(hass: HomeAssistant) -> None: """A rejected local bridge token remains inside the form flow.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) with patch( "custom_components.mb_secure.api.BridgeClient.async_get_info", side_effect=BridgeAuthenticationError, ): result = await hass.config_entries.flow.async_configure( result["flow_id"], USER_INPUT ) assert result["type"] is FlowResultType.FORM assert result["errors"] == {"base": "invalid_auth"} async def test_user_flow_rejects_bridge_without_stable_id( hass: HomeAssistant, ) -> None: """A bridge without an installation ID cannot create stable entities.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) info = BridgeInfo( bridge_id=None, bridge_version="0.1.0", api_version=1, connected=False, capabilities=frozenset(), ) with patch( "custom_components.mb_secure.api.BridgeClient.async_get_info", return_value=info, ): result = await hass.config_entries.flow.async_configure( result["flow_id"], USER_INPUT ) assert result["type"] is FlowResultType.FORM assert result["errors"] == {"base": "missing_bridge_id"} async def test_hassio_discovery_requires_confirmation_and_uses_bridge_id( hass: HomeAssistant, ) -> None: """Supervisor discovery remains user-confirmed and adopts the bridge ID.""" discovery = HassioServiceInfo( config=USER_INPUT, name="MB-Secure Bridge", slug="mb_secure_bridge", uuid="discovery-installation-1", ) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_HASSIO}, data=discovery, ) assert result["type"] is FlowResultType.FORM assert result["step_id"] == "hassio_confirm" info = BridgeInfo( bridge_id="bridge-installation-1", bridge_version="0.1.0", api_version=1, connected=True, capabilities=frozenset({"snapshot", "events"}), ) with patch( "custom_components.mb_secure.api.BridgeClient.async_get_info", return_value=info, ): result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) assert result["type"] is FlowResultType.CREATE_ENTRY assert result["result"].unique_id == "bridge-installation-1" assert result["data"] == USER_INPUT async def test_hassio_discovery_rejects_incomplete_data( hass: HomeAssistant, ) -> None: """Supervisor discovery never guesses missing connection credentials.""" discovery = HassioServiceInfo( config={"host": "bridge.local"}, name="MB-Secure Bridge", slug="mb_secure_bridge", uuid="discovery-installation-1", ) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_HASSIO}, data=discovery, ) assert result["type"] is FlowResultType.ABORT assert result["reason"] == "invalid_discovery" async def test_reauth_updates_token_for_same_bridge(hass: HomeAssistant) -> None: """A replacement token is accepted only for the configured bridge.""" entry = MockConfigEntry( domain=DOMAIN, data=USER_INPUT, unique_id="bridge-installation-1", ) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_REAUTH, "entry_id": entry.entry_id}, data=entry.data, ) info = BridgeInfo( bridge_id="bridge-installation-1", bridge_version="0.1.0", api_version=1, connected=True, capabilities=frozenset(), ) new_token = "y" * 32 with patch( "custom_components.mb_secure.api.BridgeClient.async_get_info", return_value=info, ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_TOKEN: new_token} ) assert result["type"] is FlowResultType.ABORT assert result["reason"] == "reauth_successful" assert entry.data[CONF_TOKEN] == new_token async def test_reauth_rejects_token_for_different_bridge( hass: HomeAssistant, ) -> None: """A valid token from another installation cannot replace the token.""" entry = MockConfigEntry( domain=DOMAIN, data=USER_INPUT, unique_id="bridge-installation-1", ) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_REAUTH, "entry_id": entry.entry_id}, data=entry.data, ) info = BridgeInfo( bridge_id="bridge-installation-2", bridge_version="0.1.0", api_version=1, connected=True, capabilities=frozenset(), ) with patch( "custom_components.mb_secure.api.BridgeClient.async_get_info", return_value=info, ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_TOKEN: "y" * 32} ) assert result["type"] is FlowResultType.ABORT assert result["reason"] == "wrong_account" assert entry.data[CONF_TOKEN] == USER_INPUT[CONF_TOKEN]