mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 17:57:55 +00:00

* Cache subscription url in SmartThings * Cache subscription url in SmartThings * Fix * Fix * Fix * Fix * Fix * Fix * Fix * Fix * Bump pysmartthings to 2.7.1 * 2.7.2 --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
614 lines
22 KiB
Python
614 lines
22 KiB
Python
"""Tests for the SmartThings config flow module."""
|
|
|
|
from http import HTTPStatus
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
|
|
from homeassistant.components.smartthings import OLD_DATA
|
|
from homeassistant.components.smartthings.const import (
|
|
CONF_INSTALLED_APP_ID,
|
|
CONF_LOCATION_ID,
|
|
CONF_REFRESH_TOKEN,
|
|
CONF_SUBSCRIPTION_ID,
|
|
DOMAIN,
|
|
)
|
|
from homeassistant.config_entries import SOURCE_USER, ConfigEntryState
|
|
from homeassistant.const import (
|
|
CONF_ACCESS_TOKEN,
|
|
CONF_CLIENT_ID,
|
|
CONF_CLIENT_SECRET,
|
|
CONF_TOKEN,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.data_entry_flow import FlowResultType
|
|
from homeassistant.helpers import config_entry_oauth2_flow
|
|
|
|
from tests.common import MockConfigEntry
|
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
|
from tests.typing import ClientSessionGenerator
|
|
|
|
|
|
@pytest.fixture
|
|
def use_cloud(hass: HomeAssistant) -> None:
|
|
"""Set up the cloud component."""
|
|
hass.config.components.add("cloud")
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "use_cloud")
|
|
async def test_full_flow(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_smartthings: AsyncMock,
|
|
mock_setup_entry: AsyncMock,
|
|
) -> None:
|
|
"""Check a full flow."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": SOURCE_USER}
|
|
)
|
|
|
|
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
|
|
result["data"]["token"].pop("expires_at")
|
|
assert result["data"][CONF_TOKEN] == {
|
|
"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",
|
|
}
|
|
assert result["result"].unique_id == "397678e5-9995-4a39-9d9f-ae6ba310236c"
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "use_cloud")
|
|
async def test_not_enough_scopes(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_smartthings: AsyncMock,
|
|
mock_setup_entry: AsyncMock,
|
|
) -> None:
|
|
"""Test we abort if we don't have enough scopes."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": SOURCE_USER}
|
|
)
|
|
|
|
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:* "
|
|
"r:installedapps w:installedapps",
|
|
"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.ABORT
|
|
assert result["reason"] == "missing_scopes"
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "use_cloud")
|
|
async def test_duplicate_entry(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_smartthings: AsyncMock,
|
|
mock_setup_entry: AsyncMock,
|
|
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_USER}
|
|
)
|
|
|
|
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.ABORT
|
|
assert result["reason"] == "already_configured"
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host")
|
|
async def test_no_cloud(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_smartthings: AsyncMock,
|
|
mock_setup_entry: AsyncMock,
|
|
) -> None:
|
|
"""Check we abort when cloud is not enabled."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": SOURCE_USER}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "cloud_not_enabled"
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "use_cloud")
|
|
async def test_reauthentication(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_smartthings: AsyncMock,
|
|
mock_setup_entry: AsyncMock,
|
|
mock_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test SmartThings reauthentication."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
result = await mock_config_entry.start_reauth_flow(hass)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reauth_confirm"
|
|
|
|
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",
|
|
},
|
|
)
|
|
client = await hass_client_no_auth()
|
|
await client.get(f"/auth/external/callback?code=abcd&state={state}")
|
|
|
|
aioclient_mock.post(
|
|
"https://auth-global.api.smartthings.com/oauth/token",
|
|
json={
|
|
"refresh_token": "new-refresh-token",
|
|
"access_token": "new-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:* sse w:rules:*",
|
|
"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.ABORT
|
|
assert result["reason"] == "reauth_successful"
|
|
mock_config_entry.data["token"].pop("expires_at")
|
|
assert mock_config_entry.data[CONF_TOKEN] == {
|
|
"refresh_token": "new-refresh-token",
|
|
"access_token": "new-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:* sse w:rules:*",
|
|
"access_tier": 0,
|
|
"installed_app_id": "5aaaa925-2be1-4e40-b257-e4ef59083324",
|
|
}
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "use_cloud")
|
|
async def test_reauthentication_wrong_scopes(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_smartthings: AsyncMock,
|
|
mock_setup_entry: AsyncMock,
|
|
mock_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test SmartThings reauthentication with wrong scopes."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
result = await mock_config_entry.start_reauth_flow(hass)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reauth_confirm"
|
|
|
|
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",
|
|
},
|
|
)
|
|
client = await hass_client_no_auth()
|
|
await client.get(f"/auth/external/callback?code=abcd&state={state}")
|
|
|
|
aioclient_mock.post(
|
|
"https://auth-global.api.smartthings.com/oauth/token",
|
|
json={
|
|
"refresh_token": "new-refresh-token",
|
|
"access_token": "new-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:* "
|
|
"r:installedapps w:installedapps",
|
|
"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.ABORT
|
|
assert result["reason"] == "missing_scopes"
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "use_cloud")
|
|
async def test_reauth_account_mismatch(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_smartthings: AsyncMock,
|
|
mock_setup_entry: AsyncMock,
|
|
mock_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test SmartThings reauthentication with different account."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
mock_smartthings.get_locations.return_value[
|
|
0
|
|
].location_id = "123123123-2be1-4e40-b257-e4ef59083324"
|
|
|
|
result = await mock_config_entry.start_reauth_flow(hass)
|
|
|
|
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",
|
|
},
|
|
)
|
|
client = await hass_client_no_auth()
|
|
await client.get(f"/auth/external/callback?code=abcd&state={state}")
|
|
|
|
aioclient_mock.post(
|
|
"https://auth-global.api.smartthings.com/oauth/token",
|
|
json={
|
|
"refresh_token": "new-refresh-token",
|
|
"access_token": "new-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": "123123123-2be1-4e40-b257-e4ef59083324",
|
|
},
|
|
)
|
|
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reauth_account_mismatch"
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host")
|
|
async def test_reauthentication_no_cloud(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_smartthings: AsyncMock,
|
|
mock_setup_entry: AsyncMock,
|
|
mock_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test SmartThings reauthentication without cloud."""
|
|
mock_config_entry.add_to_hass(hass)
|
|
|
|
result = await mock_config_entry.start_reauth_flow(hass)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "reauth_confirm"
|
|
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "cloud_not_enabled"
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "use_cloud")
|
|
async def test_migration(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_smartthings: AsyncMock,
|
|
mock_old_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test SmartThings reauthentication with different account."""
|
|
mock_old_config_entry.add_to_hass(hass)
|
|
|
|
await hass.config_entries.async_setup(mock_old_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_old_config_entry.state is ConfigEntryState.SETUP_ERROR
|
|
|
|
result = hass.config_entries.flow.async_progress()[0]
|
|
|
|
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",
|
|
},
|
|
)
|
|
client = await hass_client_no_auth()
|
|
await client.get(f"/auth/external/callback?code=abcd&state={state}")
|
|
|
|
aioclient_mock.post(
|
|
"https://auth-global.api.smartthings.com/oauth/token",
|
|
json={
|
|
"refresh_token": "new-refresh-token",
|
|
"access_token": "new-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": "123123123-2be1-4e40-b257-e4ef59083324",
|
|
},
|
|
)
|
|
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reauth_successful"
|
|
assert mock_old_config_entry.state is ConfigEntryState.LOADED
|
|
assert len(hass.config_entries.flow.async_progress()) == 0
|
|
mock_old_config_entry.data[CONF_TOKEN].pop("expires_at")
|
|
assert mock_old_config_entry.data == {
|
|
"auth_implementation": DOMAIN,
|
|
"old_data": {
|
|
CONF_ACCESS_TOKEN: "mock-access-token",
|
|
CONF_REFRESH_TOKEN: "mock-refresh-token",
|
|
CONF_CLIENT_ID: "CLIENT_ID",
|
|
CONF_CLIENT_SECRET: "CLIENT_SECRET",
|
|
CONF_LOCATION_ID: "397678e5-9995-4a39-9d9f-ae6ba310236c",
|
|
CONF_INSTALLED_APP_ID: "123aa123-2be1-4e40-b257-e4ef59083324",
|
|
},
|
|
CONF_TOKEN: {
|
|
"refresh_token": "new-refresh-token",
|
|
"access_token": "new-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": "123123123-2be1-4e40-b257-e4ef59083324",
|
|
},
|
|
CONF_LOCATION_ID: "397678e5-9995-4a39-9d9f-ae6ba310236c",
|
|
CONF_SUBSCRIPTION_ID: "f5768ce8-c9e5-4507-9020-912c0c60e0ab",
|
|
}
|
|
assert mock_old_config_entry.unique_id == "397678e5-9995-4a39-9d9f-ae6ba310236c"
|
|
assert mock_old_config_entry.version == 3
|
|
assert mock_old_config_entry.minor_version == 1
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host", "use_cloud")
|
|
async def test_migration_wrong_location(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_smartthings: AsyncMock,
|
|
mock_old_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test SmartThings reauthentication with wrong location."""
|
|
mock_old_config_entry.add_to_hass(hass)
|
|
|
|
await hass.config_entries.async_setup(mock_old_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_old_config_entry.state is ConfigEntryState.SETUP_ERROR
|
|
|
|
mock_smartthings.get_locations.return_value[
|
|
0
|
|
].location_id = "123123123-2be1-4e40-b257-e4ef59083324"
|
|
|
|
result = hass.config_entries.flow.async_progress()[0]
|
|
|
|
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",
|
|
},
|
|
)
|
|
client = await hass_client_no_auth()
|
|
await client.get(f"/auth/external/callback?code=abcd&state={state}")
|
|
|
|
aioclient_mock.post(
|
|
"https://auth-global.api.smartthings.com/oauth/token",
|
|
json={
|
|
"refresh_token": "new-refresh-token",
|
|
"access_token": "new-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": "123123123-2be1-4e40-b257-e4ef59083324",
|
|
},
|
|
)
|
|
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
await hass.async_block_till_done()
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "reauth_location_mismatch"
|
|
assert mock_old_config_entry.state is ConfigEntryState.SETUP_ERROR
|
|
assert mock_old_config_entry.data == {
|
|
OLD_DATA: {
|
|
CONF_ACCESS_TOKEN: "mock-access-token",
|
|
CONF_REFRESH_TOKEN: "mock-refresh-token",
|
|
CONF_CLIENT_ID: "CLIENT_ID",
|
|
CONF_CLIENT_SECRET: "CLIENT_SECRET",
|
|
CONF_LOCATION_ID: "397678e5-9995-4a39-9d9f-ae6ba310236c",
|
|
CONF_INSTALLED_APP_ID: "123aa123-2be1-4e40-b257-e4ef59083324",
|
|
}
|
|
}
|
|
assert (
|
|
mock_old_config_entry.unique_id
|
|
== "appid123-2be1-4e40-b257-e4ef59083324_397678e5-9995-4a39-9d9f-ae6ba310236c"
|
|
)
|
|
assert mock_old_config_entry.version == 3
|
|
assert mock_old_config_entry.minor_version == 1
|
|
|
|
|
|
@pytest.mark.usefixtures("current_request_with_host")
|
|
async def test_migration_no_cloud(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
aioclient_mock: AiohttpClientMocker,
|
|
mock_smartthings: AsyncMock,
|
|
mock_old_config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test SmartThings reauthentication with different account."""
|
|
mock_old_config_entry.add_to_hass(hass)
|
|
|
|
await hass.config_entries.async_setup(mock_old_config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_old_config_entry.state is ConfigEntryState.SETUP_ERROR
|
|
|
|
result = hass.config_entries.flow.async_progress()[0]
|
|
|
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
|
|
|
assert result["type"] is FlowResultType.ABORT
|
|
assert result["reason"] == "cloud_not_enabled"
|