1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 12:59:34 +00:00

Store unique user configurations for HomeLink integration (#159111)

This commit is contained in:
ryanjones-gentex
2025-12-16 11:14:49 -05:00
committed by GitHub
parent fe34da19e2
commit 36aefce9e1
5 changed files with 53 additions and 13 deletions

View File

@@ -5,6 +5,7 @@ from typing import Any
import botocore.exceptions import botocore.exceptions
from homelink.auth.srp_auth import SRPAuth from homelink.auth.srp_auth import SRPAuth
import jwt
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ConfigFlowResult from homeassistant.config_entries import ConfigFlowResult
@@ -38,8 +39,6 @@ class SRPFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN):
"""Ask for username and password.""" """Ask for username and password."""
errors: dict[str, str] = {} errors: dict[str, str] = {}
if user_input is not None: if user_input is not None:
self._async_abort_entries_match({CONF_EMAIL: user_input[CONF_EMAIL]})
srp_auth = SRPAuth() srp_auth = SRPAuth()
try: try:
tokens = await self.hass.async_add_executor_job( tokens = await self.hass.async_add_executor_job(
@@ -48,12 +47,17 @@ class SRPFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN):
user_input[CONF_PASSWORD], user_input[CONF_PASSWORD],
) )
except botocore.exceptions.ClientError: except botocore.exceptions.ClientError:
_LOGGER.exception("Error authenticating homelink account")
errors["base"] = "srp_auth_failed" errors["base"] = "srp_auth_failed"
except Exception: except Exception:
_LOGGER.exception("An unexpected error occurred") _LOGGER.exception("An unexpected error occurred")
errors["base"] = "unknown" errors["base"] = "unknown"
else: else:
access_token = jwt.decode(
tokens["AuthenticationResult"]["AccessToken"],
options={"verify_signature": False},
)
await self.async_set_unique_id(access_token["sub"])
self._abort_if_unique_id_configured()
self.external_data = {"tokens": tokens} self.external_data = {"tokens": tokens}
return await self.async_step_creation() return await self.async_step_creation()

View File

@@ -1,10 +1,9 @@
"""Makes requests to the state server and stores the resulting data so that the buttons can access it.""" """Establish MQTT connection and listen for event data."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from functools import partial from functools import partial
import logging
from typing import TypedDict from typing import TypedDict
from homelink.model.device import Device from homelink.model.device import Device
@@ -14,8 +13,6 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.util.ssl import get_default_context from homeassistant.util.ssl import get_default_context
_LOGGER = logging.getLogger(__name__)
type HomeLinkConfigEntry = ConfigEntry[HomeLinkCoordinator] type HomeLinkConfigEntry = ConfigEntry[HomeLinkCoordinator]
type EventCallback = Callable[[HomeLinkEventData], None] type EventCallback = Callable[[HomeLinkEventData], None]

View File

@@ -3,10 +3,17 @@
from typing import Any from typing import Any
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
import jwt
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
TEST_CREDENTIALS = {CONF_EMAIL: "test@test.com", CONF_PASSWORD: "SomePassword"}
TEST_ACCESS_JWT = jwt.encode({"sub": "some-uuid"}, key="secret")
async def setup_integration(hass: HomeAssistant, entry: MockConfigEntry) -> None: async def setup_integration(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Set up the homelink integration for testing.""" """Set up the homelink integration for testing."""

View File

@@ -9,6 +9,8 @@ import pytest
from homeassistant.components.gentex_homelink import DOMAIN from homeassistant.components.gentex_homelink import DOMAIN
from . import TEST_ACCESS_JWT
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@@ -21,7 +23,7 @@ def mock_srp_auth() -> Generator[AsyncMock]:
instance = mock_srp_auth.return_value instance = mock_srp_auth.return_value
instance.async_get_access_token.return_value = { instance.async_get_access_token.return_value = {
"AuthenticationResult": { "AuthenticationResult": {
"AccessToken": "access", "AccessToken": TEST_ACCESS_JWT,
"RefreshToken": "refresh", "RefreshToken": "refresh",
"TokenType": "bearer", "TokenType": "bearer",
"ExpiresIn": 3600, "ExpiresIn": 3600,
@@ -60,6 +62,8 @@ def mock_device() -> AsyncMock:
def mock_config_entry() -> MockConfigEntry: def mock_config_entry() -> MockConfigEntry:
"""Mock setup entry.""" """Mock setup entry."""
return MockConfigEntry( return MockConfigEntry(
unique_id="some-uuid",
version=1,
domain=DOMAIN, domain=DOMAIN,
data={ data={
"auth_implementation": "gentex_homelink", "auth_implementation": "gentex_homelink",

View File

@@ -7,10 +7,13 @@ import pytest
from homeassistant.components.gentex_homelink.const import DOMAIN from homeassistant.components.gentex_homelink.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from . import TEST_ACCESS_JWT, TEST_CREDENTIALS, setup_integration
from tests.common import MockConfigEntry
async def test_full_flow( async def test_full_flow(
hass: HomeAssistant, mock_srp_auth: AsyncMock, mock_setup_entry: AsyncMock hass: HomeAssistant, mock_srp_auth: AsyncMock, mock_setup_entry: AsyncMock
@@ -26,13 +29,13 @@ async def test_full_flow(
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={CONF_EMAIL: "test@test.com", CONF_PASSWORD: "SomePassword"}, user_input=TEST_CREDENTIALS,
) )
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == { assert result["data"] == {
"auth_implementation": "gentex_homelink", "auth_implementation": "gentex_homelink",
"token": { "token": {
"access_token": "access", "access_token": TEST_ACCESS_JWT,
"refresh_token": "refresh", "refresh_token": "refresh",
"expires_in": 3600, "expires_in": 3600,
"token_type": "bearer", "token_type": "bearer",
@@ -40,6 +43,31 @@ async def test_full_flow(
}, },
} }
assert result["title"] == "SRPAuth" assert result["title"] == "SRPAuth"
assert result["result"].unique_id == "some-uuid"
async def test_unique_configurations(
hass: HomeAssistant,
mock_srp_auth: AsyncMock,
mock_setup_entry: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Check full flow."""
await setup_integration(hass, mock_config_entry)
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 not result["errors"]
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=TEST_CREDENTIALS,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
@pytest.mark.parametrize( @pytest.mark.parametrize(
@@ -69,7 +97,7 @@ async def test_exceptions(
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={CONF_EMAIL: "test@test.com", CONF_PASSWORD: "SomePassword"}, user_input=TEST_CREDENTIALS,
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
@@ -79,6 +107,6 @@ async def test_exceptions(
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={CONF_EMAIL: "test@test.com", CONF_PASSWORD: "SomePassword"}, user_input=TEST_CREDENTIALS,
) )
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY