diff --git a/homeassistant/components/smartthings/manifest.json b/homeassistant/components/smartthings/manifest.json index 17aababd641..a30bcaee30a 100644 --- a/homeassistant/components/smartthings/manifest.json +++ b/homeassistant/components/smartthings/manifest.json @@ -24,6 +24,9 @@ { "hostname": "hub*", "macaddress": "286D97*" + }, + { + "hostname": "samsung-*" } ], "documentation": "https://www.home-assistant.io/integrations/smartthings", diff --git a/homeassistant/components/smartthings/strings.json b/homeassistant/components/smartthings/strings.json index 6a1d69ec866..625f8786259 100644 --- a/homeassistant/components/smartthings/strings.json +++ b/homeassistant/components/smartthings/strings.json @@ -23,6 +23,9 @@ "user": "[%key:common::config_flow::initiate_flow::account%]" }, "step": { + "oauth_discovery": { + "description": "Home Assistant has found a SmartThings device on your network. Press **Submit** to continue setting up SmartThings." + }, "pick_implementation": { "data": { "implementation": "[%key:common::config_flow::data::implementation%]" diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index e650435a2e0..d3dde435250 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -813,6 +813,10 @@ DHCP: Final[list[dict[str, str | bool]]] = [ "hostname": "hub*", "macaddress": "286D97*", }, + { + "domain": "smartthings", + "hostname": "samsung-*", + }, { "domain": "smlight", "registered_devices": True, diff --git a/tests/components/smartthings/test_config_flow.py b/tests/components/smartthings/test_config_flow.py index 02011ac897b..253e0ec2793 100644 --- a/tests/components/smartthings/test_config_flow.py +++ b/tests/components/smartthings/test_config_flow.py @@ -11,11 +11,12 @@ from homeassistant.components.smartthings.const import ( CONF_SUBSCRIPTION_ID, DOMAIN, ) -from homeassistant.config_entries import SOURCE_USER, ConfigEntryState +from homeassistant.config_entries import SOURCE_DHCP, SOURCE_USER, ConfigEntryState from homeassistant.const import CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType from homeassistant.helpers import config_entry_oauth2_flow +from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo from tests.common import MockConfigEntry from tests.test_util.aiohttp import AiohttpClientMocker @@ -614,3 +615,89 @@ async def test_migration_no_cloud( assert result["type"] is FlowResultType.ABORT assert result["reason"] == "cloud_not_enabled" + + +@pytest.mark.usefixtures("current_request_with_host", "use_cloud") +async def test_dhcp_flow( + hass: HomeAssistant, + hass_client_no_auth: ClientSessionGenerator, + aioclient_mock: AiohttpClientMocker, + mock_smartthings: AsyncMock, + mock_setup_entry: AsyncMock, +) -> None: + """Check a full flow initiated by DHCP discovery.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_DHCP}, + data=DhcpServiceInfo( + ip="192.168.0.2", hostname="Samsung-Washer", macaddress="88571dc3ed7d" + ), + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "oauth_discovery" + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + + assert result["type"] is FlowResultType.EXTERNAL_STEP + assert result["url"] == ( + "https://api.smartthings.com/oauth/authorize" + "?response_type=code&client_id=CLIENT_ID" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + "&scope=r:devices:*+w:devices:*+x:devices:*+r:hubs:*+" + "r:locations:*+w:locations:*+x:locations:*+r:scenes:*+" + "x:scenes:*+r:rules:*+w:rules:*+sse+r:installedapps+" + "w:installedapps" + ) + + client = await hass_client_no_auth() + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == HTTPStatus.OK + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.clear_requests() + aioclient_mock.post( + "https://auth-global.api.smartthings.com/oauth/token", + json={ + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "token_type": "Bearer", + "expires_in": 82806, + "scope": "r:devices:* w:devices:* x:devices:* r:hubs:* " + "r:locations:* w:locations:* x:locations:* " + "r:scenes:* x:scenes:* r:rules:* w:rules:* sse", + "access_tier": 0, + "installed_app_id": "5aaaa925-2be1-4e40-b257-e4ef59083324", + }, + ) + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert result["type"] is FlowResultType.CREATE_ENTRY + + +@pytest.mark.usefixtures("current_request_with_host", "use_cloud") +async def test_duplicate_entry_dhcp( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> None: + """Test duplicate entry is not able to set up.""" + mock_config_entry.add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_DHCP}, + data=DhcpServiceInfo( + ip="192.168.0.2", hostname="Samsung-Washer", macaddress="88571dc3ed7d" + ), + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured"