core/tests/components/smartthings/test_config_flow.py

349 lines
13 KiB
Python

"""Tests for the SmartThings config flow module."""
from unittest.mock import Mock, patch
from uuid import uuid4
from aiohttp import ClientResponseError
from pysmartthings import APIResponseError
from homeassistant import data_entry_flow
from homeassistant.components import cloud
from homeassistant.components.smartthings import smartapp
from homeassistant.components.smartthings.config_flow import (
SmartThingsFlowHandler)
from homeassistant.components.smartthings.const import (
CONF_INSTALLED_APP_ID, CONF_INSTALLED_APPS, CONF_LOCATION_ID,
CONF_REFRESH_TOKEN, DOMAIN)
from homeassistant.config_entries import ConfigEntry
from tests.common import mock_coro
async def test_step_user(hass):
"""Test the access token form is shown for a user initiated flow."""
flow = SmartThingsFlowHandler()
flow.hass = hass
result = await flow.async_step_user()
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'user'
async def test_step_init(hass):
"""Test the access token form is shown for an init flow."""
flow = SmartThingsFlowHandler()
flow.hass = hass
result = await flow.async_step_import()
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'user'
async def test_base_url_not_https(hass):
"""Test the base_url parameter starts with https://."""
hass.config.api.base_url = 'http://0.0.0.0'
flow = SmartThingsFlowHandler()
flow.hass = hass
result = await flow.async_step_user({'access_token': str(uuid4())})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'user'
assert result['errors'] == {'base': 'base_url_not_https'}
async def test_invalid_token_format(hass):
"""Test an error is shown for invalid token formats."""
flow = SmartThingsFlowHandler()
flow.hass = hass
result = await flow.async_step_user({'access_token': '123456789'})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'user'
assert result['errors'] == {'access_token': 'token_invalid_format'}
async def test_token_already_setup(hass):
"""Test an error is shown when the token is already setup."""
flow = SmartThingsFlowHandler()
flow.hass = hass
token = str(uuid4())
entries = [ConfigEntry(
version='', domain='', title='', data={'access_token': token},
source='', connection_class='')]
with patch.object(hass.config_entries, 'async_entries',
return_value=entries):
result = await flow.async_step_user({'access_token': token})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'user'
assert result['errors'] == {'access_token': 'token_already_setup'}
async def test_token_unauthorized(hass, smartthings_mock):
"""Test an error is shown when the token is not authorized."""
flow = SmartThingsFlowHandler()
flow.hass = hass
smartthings_mock.return_value.apps.return_value = mock_coro(
exception=ClientResponseError(None, None, status=401))
result = await flow.async_step_user({'access_token': str(uuid4())})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'user'
assert result['errors'] == {'access_token': 'token_unauthorized'}
async def test_token_forbidden(hass, smartthings_mock):
"""Test an error is shown when the token is forbidden."""
flow = SmartThingsFlowHandler()
flow.hass = hass
smartthings_mock.return_value.apps.return_value = mock_coro(
exception=ClientResponseError(None, None, status=403))
result = await flow.async_step_user({'access_token': str(uuid4())})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'user'
assert result['errors'] == {'access_token': 'token_forbidden'}
async def test_webhook_error(hass, smartthings_mock):
"""Test an error is when there's an error with the webhook endpoint."""
flow = SmartThingsFlowHandler()
flow.hass = hass
data = {'error': {}}
error = APIResponseError(None, None, data=data, status=422)
error.is_target_error = Mock(return_value=True)
smartthings_mock.return_value.apps.return_value = mock_coro(
exception=error)
result = await flow.async_step_user({'access_token': str(uuid4())})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'user'
assert result['errors'] == {'base': 'webhook_error'}
async def test_api_error(hass, smartthings_mock):
"""Test an error is shown when other API errors occur."""
flow = SmartThingsFlowHandler()
flow.hass = hass
data = {'error': {}}
error = APIResponseError(None, None, data=data, status=400)
smartthings_mock.return_value.apps.return_value = mock_coro(
exception=error)
result = await flow.async_step_user({'access_token': str(uuid4())})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'user'
assert result['errors'] == {'base': 'app_setup_error'}
async def test_unknown_api_error(hass, smartthings_mock):
"""Test an error is shown when there is an unknown API error."""
flow = SmartThingsFlowHandler()
flow.hass = hass
smartthings_mock.return_value.apps.return_value = mock_coro(
exception=ClientResponseError(None, None, status=404))
result = await flow.async_step_user({'access_token': str(uuid4())})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'user'
assert result['errors'] == {'base': 'app_setup_error'}
async def test_unknown_error(hass, smartthings_mock):
"""Test an error is shown when there is an unknown API error."""
flow = SmartThingsFlowHandler()
flow.hass = hass
smartthings_mock.return_value.apps.return_value = mock_coro(
exception=Exception('Unknown error'))
result = await flow.async_step_user({'access_token': str(uuid4())})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'user'
assert result['errors'] == {'base': 'app_setup_error'}
async def test_app_created_then_show_wait_form(
hass, app, app_oauth_client, smartthings_mock):
"""Test SmartApp is created when one does not exist and shows wait form."""
flow = SmartThingsFlowHandler()
flow.hass = hass
smartthings = smartthings_mock.return_value
smartthings.apps.return_value = mock_coro(return_value=[])
smartthings.create_app.return_value = \
mock_coro(return_value=(app, app_oauth_client))
smartthings.update_app_settings.return_value = mock_coro()
smartthings.update_app_oauth.return_value = mock_coro()
result = await flow.async_step_user({'access_token': str(uuid4())})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'wait_install'
async def test_cloudhook_app_created_then_show_wait_form(
hass, app, app_oauth_client, smartthings_mock):
"""Test SmartApp is created with a cloudhoko and shows wait form."""
# Unload the endpoint so we can reload it under the cloud.
await smartapp.unload_smartapp_endpoint(hass)
mock_async_active_subscription = Mock(return_value=True)
mock_create_cloudhook = Mock(return_value=mock_coro(
return_value="http://cloud.test"))
with patch.object(cloud, 'async_active_subscription',
new=mock_async_active_subscription), \
patch.object(cloud, 'async_create_cloudhook',
new=mock_create_cloudhook):
await smartapp.setup_smartapp_endpoint(hass)
flow = SmartThingsFlowHandler()
flow.hass = hass
smartthings = smartthings_mock.return_value
smartthings.apps.return_value = mock_coro(return_value=[])
smartthings.create_app.return_value = \
mock_coro(return_value=(app, app_oauth_client))
smartthings.update_app_settings.return_value = mock_coro()
smartthings.update_app_oauth.return_value = mock_coro()
result = await flow.async_step_user({'access_token': str(uuid4())})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'wait_install'
assert mock_create_cloudhook.call_count == 1
async def test_app_updated_then_show_wait_form(
hass, app, app_oauth_client, smartthings_mock):
"""Test SmartApp is updated when an existing is already created."""
flow = SmartThingsFlowHandler()
flow.hass = hass
api = smartthings_mock.return_value
api.apps.return_value = mock_coro(return_value=[app])
api.generate_app_oauth.return_value = \
mock_coro(return_value=app_oauth_client)
result = await flow.async_step_user({'access_token': str(uuid4())})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'wait_install'
async def test_wait_form_displayed(hass):
"""Test the wait for installation form is displayed."""
flow = SmartThingsFlowHandler()
flow.hass = hass
result = await flow.async_step_wait_install(None)
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'wait_install'
async def test_wait_form_displayed_after_checking(hass, smartthings_mock):
"""Test error is shown when the user has not installed the app."""
flow = SmartThingsFlowHandler()
flow.hass = hass
flow.access_token = str(uuid4())
result = await flow.async_step_wait_install({})
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
assert result['step_id'] == 'wait_install'
assert result['errors'] == {'base': 'app_not_installed'}
async def test_config_entry_created_when_installed(
hass, location, installed_app, smartthings_mock):
"""Test a config entry is created once the app is installed."""
flow = SmartThingsFlowHandler()
flow.hass = hass
flow.access_token = str(uuid4())
flow.app_id = installed_app.app_id
flow.api = smartthings_mock.return_value
flow.oauth_client_id = str(uuid4())
flow.oauth_client_secret = str(uuid4())
data = {
CONF_REFRESH_TOKEN: str(uuid4()),
CONF_LOCATION_ID: installed_app.location_id,
CONF_INSTALLED_APP_ID: installed_app.installed_app_id
}
hass.data[DOMAIN][CONF_INSTALLED_APPS].append(data)
result = await flow.async_step_wait_install({})
assert not hass.data[DOMAIN][CONF_INSTALLED_APPS]
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result['data']['app_id'] == installed_app.app_id
assert result['data']['installed_app_id'] == \
installed_app.installed_app_id
assert result['data']['location_id'] == installed_app.location_id
assert result['data']['access_token'] == flow.access_token
assert result['data']['refresh_token'] == data[CONF_REFRESH_TOKEN]
assert result['data']['client_secret'] == flow.oauth_client_secret
assert result['data']['client_id'] == flow.oauth_client_id
assert result['title'] == location.name
async def test_multiple_config_entry_created_when_installed(
hass, app, locations, installed_apps, smartthings_mock):
"""Test a config entries are created for multiple installs."""
flow = SmartThingsFlowHandler()
flow.hass = hass
flow.access_token = str(uuid4())
flow.app_id = app.app_id
flow.api = smartthings_mock.return_value
flow.oauth_client_id = str(uuid4())
flow.oauth_client_secret = str(uuid4())
for installed_app in installed_apps:
data = {
CONF_REFRESH_TOKEN: str(uuid4()),
CONF_LOCATION_ID: installed_app.location_id,
CONF_INSTALLED_APP_ID: installed_app.installed_app_id
}
hass.data[DOMAIN][CONF_INSTALLED_APPS].append(data)
install_data = hass.data[DOMAIN][CONF_INSTALLED_APPS].copy()
result = await flow.async_step_wait_install({})
assert not hass.data[DOMAIN][CONF_INSTALLED_APPS]
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result['data']['app_id'] == installed_apps[0].app_id
assert result['data']['installed_app_id'] == \
installed_apps[0].installed_app_id
assert result['data']['location_id'] == installed_apps[0].location_id
assert result['data']['access_token'] == flow.access_token
assert result['data']['refresh_token'] == \
install_data[0][CONF_REFRESH_TOKEN]
assert result['data']['client_secret'] == flow.oauth_client_secret
assert result['data']['client_id'] == flow.oauth_client_id
assert result['title'] == locations[0].name
await hass.async_block_till_done()
entries = hass.config_entries.async_entries('smartthings')
assert len(entries) == 1
assert entries[0].data['app_id'] == installed_apps[1].app_id
assert entries[0].data['installed_app_id'] == \
installed_apps[1].installed_app_id
assert entries[0].data['location_id'] == installed_apps[1].location_id
assert entries[0].data['access_token'] == flow.access_token
assert entries[0].data['client_secret'] == flow.oauth_client_secret
assert entries[0].data['client_id'] == flow.oauth_client_id
assert entries[0].title == locations[1].name