"""Tests for the HTTP API for the cloud component.""" import asyncio from http import HTTPStatus from unittest.mock import AsyncMock, MagicMock, Mock, patch import aiohttp from hass_nabucasa import thingtalk, voice from hass_nabucasa.auth import Unauthenticated, UnknownError from hass_nabucasa.const import STATE_CONNECTED from jose import jwt import pytest from homeassistant.components.alexa import errors as alexa_errors from homeassistant.components.alexa.entities import LightCapabilities from homeassistant.components.cloud.const import DOMAIN from homeassistant.components.google_assistant.helpers import GoogleEntity from homeassistant.core import HomeAssistant, State from homeassistant.util.location import LocationInfo from . import mock_cloud, mock_cloud_prefs from tests.components.google_assistant import MockConfig from tests.test_util.aiohttp import AiohttpClientMocker from tests.typing import WebSocketGenerator SUBSCRIPTION_INFO_URL = "https://api-test.hass.io/payments/subscription_info" @pytest.fixture(name="mock_cloud_login") def mock_cloud_login_fixture(hass, setup_api): """Mock cloud is logged in.""" hass.data[DOMAIN].id_token = jwt.encode( { "email": "hello@home-assistant.io", "custom:sub-exp": "2018-01-03", "cognito:username": "abcdefghjkl", }, "test", ) @pytest.fixture(autouse=True, name="setup_api") def setup_api_fixture(hass, aioclient_mock): """Initialize HTTP API.""" hass.loop.run_until_complete( mock_cloud( hass, { "mode": "development", "cognito_client_id": "cognito_client_id", "user_pool_id": "user_pool_id", "region": "region", "relayer_server": "relayer", "accounts_server": "api-test.hass.io", "google_actions": {"filter": {"include_domains": "light"}}, "alexa": { "filter": {"include_entities": ["light.kitchen", "switch.ac"]} }, }, ) ) return mock_cloud_prefs(hass) @pytest.fixture(name="cloud_client") def cloud_client_fixture(hass, hass_client): """Fixture that can fetch from the cloud client.""" with patch("hass_nabucasa.Cloud._write_user_info"): yield hass.loop.run_until_complete(hass_client()) @pytest.fixture(name="mock_cognito") def mock_cognito_fixture(): """Mock warrant.""" with patch("hass_nabucasa.auth.CognitoAuth._cognito") as mock_cog: yield mock_cog() async def test_google_actions_sync( mock_cognito, mock_cloud_login, cloud_client ) -> None: """Test syncing Google Actions.""" with patch( "hass_nabucasa.cloud_api.async_google_actions_request_sync", return_value=Mock(status=200), ) as mock_request_sync: req = await cloud_client.post("/api/cloud/google_actions/sync") assert req.status == HTTPStatus.OK assert len(mock_request_sync.mock_calls) == 1 async def test_google_actions_sync_fails( mock_cognito, mock_cloud_login, cloud_client ) -> None: """Test syncing Google Actions gone bad.""" with patch( "hass_nabucasa.cloud_api.async_google_actions_request_sync", return_value=Mock(status=HTTPStatus.INTERNAL_SERVER_ERROR), ) as mock_request_sync: req = await cloud_client.post("/api/cloud/google_actions/sync") assert req.status == HTTPStatus.INTERNAL_SERVER_ERROR assert len(mock_request_sync.mock_calls) == 1 async def test_login_view(hass: HomeAssistant, cloud_client) -> None: """Test logging in.""" hass.data["cloud"] = MagicMock(login=AsyncMock()) req = await cloud_client.post( "/api/cloud/login", json={"email": "my_username", "password": "my_password"} ) assert req.status == HTTPStatus.OK result = await req.json() assert result == {"success": True} async def test_login_view_random_exception(cloud_client) -> None: """Try logging in with invalid JSON.""" with patch("hass_nabucasa.Cloud.login", side_effect=ValueError("Boom")): req = await cloud_client.post( "/api/cloud/login", json={"email": "my_username", "password": "my_password"} ) assert req.status == HTTPStatus.BAD_GATEWAY resp = await req.json() assert resp == {"code": "valueerror", "message": "Unexpected error: Boom"} async def test_login_view_invalid_json(cloud_client) -> None: """Try logging in with invalid JSON.""" with patch("hass_nabucasa.auth.CognitoAuth.async_login") as mock_login: req = await cloud_client.post("/api/cloud/login", data="Not JSON") assert req.status == HTTPStatus.BAD_REQUEST assert len(mock_login.mock_calls) == 0 async def test_login_view_invalid_schema(cloud_client) -> None: """Try logging in with invalid schema.""" with patch("hass_nabucasa.auth.CognitoAuth.async_login") as mock_login: req = await cloud_client.post("/api/cloud/login", json={"invalid": "schema"}) assert req.status == HTTPStatus.BAD_REQUEST assert len(mock_login.mock_calls) == 0 async def test_login_view_request_timeout(cloud_client) -> None: """Test request timeout while trying to log in.""" with patch( "hass_nabucasa.auth.CognitoAuth.async_login", side_effect=asyncio.TimeoutError ): req = await cloud_client.post( "/api/cloud/login", json={"email": "my_username", "password": "my_password"} ) assert req.status == HTTPStatus.BAD_GATEWAY async def test_login_view_invalid_credentials(cloud_client) -> None: """Test logging in with invalid credentials.""" with patch( "hass_nabucasa.auth.CognitoAuth.async_login", side_effect=Unauthenticated ): req = await cloud_client.post( "/api/cloud/login", json={"email": "my_username", "password": "my_password"} ) assert req.status == HTTPStatus.UNAUTHORIZED async def test_login_view_unknown_error(cloud_client) -> None: """Test unknown error while logging in.""" with patch("hass_nabucasa.auth.CognitoAuth.async_login", side_effect=UnknownError): req = await cloud_client.post( "/api/cloud/login", json={"email": "my_username", "password": "my_password"} ) assert req.status == HTTPStatus.BAD_GATEWAY async def test_logout_view(hass: HomeAssistant, cloud_client) -> None: """Test logging out.""" cloud = hass.data["cloud"] = MagicMock() cloud.logout = AsyncMock(return_value=None) req = await cloud_client.post("/api/cloud/logout") assert req.status == HTTPStatus.OK data = await req.json() assert data == {"message": "ok"} assert len(cloud.logout.mock_calls) == 1 async def test_logout_view_request_timeout(hass: HomeAssistant, cloud_client) -> None: """Test timeout while logging out.""" cloud = hass.data["cloud"] = MagicMock() cloud.logout.side_effect = asyncio.TimeoutError req = await cloud_client.post("/api/cloud/logout") assert req.status == HTTPStatus.BAD_GATEWAY async def test_logout_view_unknown_error(hass: HomeAssistant, cloud_client) -> None: """Test unknown error while logging out.""" cloud = hass.data["cloud"] = MagicMock() cloud.logout.side_effect = UnknownError req = await cloud_client.post("/api/cloud/logout") assert req.status == HTTPStatus.BAD_GATEWAY async def test_register_view_no_location(mock_cognito, cloud_client) -> None: """Test register without location.""" with patch( "homeassistant.components.cloud.http_api.async_detect_location_info", return_value=None, ): req = await cloud_client.post( "/api/cloud/register", json={"email": "hello@bla.com", "password": "falcon42"}, ) assert req.status == HTTPStatus.OK assert len(mock_cognito.register.mock_calls) == 1 call = mock_cognito.register.mock_calls[0] result_email, result_pass = call.args assert result_email == "hello@bla.com" assert result_pass == "falcon42" assert call.kwargs["client_metadata"] is None async def test_register_view_with_location(mock_cognito, cloud_client) -> None: """Test register with location.""" with patch( "homeassistant.components.cloud.http_api.async_detect_location_info", return_value=LocationInfo( **{ "country_code": "XX", "zip_code": "12345", "region_code": "GH", "ip": "1.2.3.4", "city": "Gotham", "region_name": "Gotham", "time_zone": "Earth/Gotham", "currency": "XXX", "latitude": "12.34567", "longitude": "12.34567", "use_metric": True, } ), ): req = await cloud_client.post( "/api/cloud/register", json={"email": "hello@bla.com", "password": "falcon42"}, ) assert req.status == HTTPStatus.OK assert len(mock_cognito.register.mock_calls) == 1 call = mock_cognito.register.mock_calls[0] result_email, result_pass = call.args assert result_email == "hello@bla.com" assert result_pass == "falcon42" assert call.kwargs["client_metadata"] == { "NC_COUNTRY_CODE": "XX", "NC_REGION_CODE": "GH", "NC_ZIP_CODE": "12345", } async def test_register_view_bad_data(mock_cognito, cloud_client) -> None: """Test logging out.""" req = await cloud_client.post( "/api/cloud/register", json={"email": "hello@bla.com", "not_password": "falcon"} ) assert req.status == HTTPStatus.BAD_REQUEST assert len(mock_cognito.logout.mock_calls) == 0 async def test_register_view_request_timeout(mock_cognito, cloud_client) -> None: """Test timeout while logging out.""" mock_cognito.register.side_effect = asyncio.TimeoutError req = await cloud_client.post( "/api/cloud/register", json={"email": "hello@bla.com", "password": "falcon42"} ) assert req.status == HTTPStatus.BAD_GATEWAY async def test_register_view_unknown_error(mock_cognito, cloud_client) -> None: """Test unknown error while logging out.""" mock_cognito.register.side_effect = UnknownError req = await cloud_client.post( "/api/cloud/register", json={"email": "hello@bla.com", "password": "falcon42"} ) assert req.status == HTTPStatus.BAD_GATEWAY async def test_forgot_password_view(mock_cognito, cloud_client) -> None: """Test logging out.""" req = await cloud_client.post( "/api/cloud/forgot_password", json={"email": "hello@bla.com"} ) assert req.status == HTTPStatus.OK assert len(mock_cognito.initiate_forgot_password.mock_calls) == 1 async def test_forgot_password_view_bad_data(mock_cognito, cloud_client) -> None: """Test logging out.""" req = await cloud_client.post( "/api/cloud/forgot_password", json={"not_email": "hello@bla.com"} ) assert req.status == HTTPStatus.BAD_REQUEST assert len(mock_cognito.initiate_forgot_password.mock_calls) == 0 async def test_forgot_password_view_request_timeout(mock_cognito, cloud_client) -> None: """Test timeout while logging out.""" mock_cognito.initiate_forgot_password.side_effect = asyncio.TimeoutError req = await cloud_client.post( "/api/cloud/forgot_password", json={"email": "hello@bla.com"} ) assert req.status == HTTPStatus.BAD_GATEWAY async def test_forgot_password_view_unknown_error(mock_cognito, cloud_client) -> None: """Test unknown error while logging out.""" mock_cognito.initiate_forgot_password.side_effect = UnknownError req = await cloud_client.post( "/api/cloud/forgot_password", json={"email": "hello@bla.com"} ) assert req.status == HTTPStatus.BAD_GATEWAY async def test_forgot_password_view_aiohttp_error(mock_cognito, cloud_client) -> None: """Test unknown error while logging out.""" mock_cognito.initiate_forgot_password.side_effect = aiohttp.ClientResponseError( Mock(), Mock() ) req = await cloud_client.post( "/api/cloud/forgot_password", json={"email": "hello@bla.com"} ) assert req.status == HTTPStatus.INTERNAL_SERVER_ERROR async def test_resend_confirm_view(mock_cognito, cloud_client) -> None: """Test logging out.""" req = await cloud_client.post( "/api/cloud/resend_confirm", json={"email": "hello@bla.com"} ) assert req.status == HTTPStatus.OK assert len(mock_cognito.client.resend_confirmation_code.mock_calls) == 1 async def test_resend_confirm_view_bad_data(mock_cognito, cloud_client) -> None: """Test logging out.""" req = await cloud_client.post( "/api/cloud/resend_confirm", json={"not_email": "hello@bla.com"} ) assert req.status == HTTPStatus.BAD_REQUEST assert len(mock_cognito.client.resend_confirmation_code.mock_calls) == 0 async def test_resend_confirm_view_request_timeout(mock_cognito, cloud_client) -> None: """Test timeout while logging out.""" mock_cognito.client.resend_confirmation_code.side_effect = asyncio.TimeoutError req = await cloud_client.post( "/api/cloud/resend_confirm", json={"email": "hello@bla.com"} ) assert req.status == HTTPStatus.BAD_GATEWAY async def test_resend_confirm_view_unknown_error(mock_cognito, cloud_client) -> None: """Test unknown error while logging out.""" mock_cognito.client.resend_confirmation_code.side_effect = UnknownError req = await cloud_client.post( "/api/cloud/resend_confirm", json={"email": "hello@bla.com"} ) assert req.status == HTTPStatus.BAD_GATEWAY async def test_websocket_status( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, mock_cloud_fixture, mock_cloud_login, ) -> None: """Test querying the status.""" hass.data[DOMAIN].iot.state = STATE_CONNECTED client = await hass_ws_client(hass) with patch.dict( "homeassistant.components.google_assistant.const.DOMAIN_TO_GOOGLE_TYPES", {"light": None}, clear=True, ), patch.dict( "homeassistant.components.alexa.entities.ENTITY_ADAPTERS", {"switch": None}, clear=True, ): await client.send_json({"id": 5, "type": "cloud/status"}) response = await client.receive_json() assert response["result"] == { "logged_in": True, "email": "hello@home-assistant.io", "cloud": "connected", "cloud_last_disconnect_reason": None, "prefs": { "alexa_enabled": True, "cloudhooks": {}, "google_enabled": True, "google_entity_configs": {}, "google_secure_devices_pin": None, "google_default_expose": None, "alexa_default_expose": None, "alexa_entity_configs": {}, "alexa_report_state": True, "google_report_state": True, "remote_enabled": False, "tts_default_voice": ["en-US", "female"], }, "alexa_entities": { "include_domains": [], "include_entity_globs": [], "include_entities": ["light.kitchen", "switch.ac"], "exclude_domains": [], "exclude_entity_globs": [], "exclude_entities": [], }, "alexa_registered": False, "google_entities": { "include_domains": ["light"], "include_entity_globs": [], "include_entities": [], "exclude_domains": [], "exclude_entity_globs": [], "exclude_entities": [], }, "google_registered": False, "google_local_connected": False, "remote_domain": None, "remote_connected": False, "remote_certificate": None, "http_use_ssl": False, "active_subscription": False, } async def test_websocket_status_not_logged_in( hass: HomeAssistant, hass_ws_client: WebSocketGenerator ) -> None: """Test querying the status.""" client = await hass_ws_client(hass) await client.send_json({"id": 5, "type": "cloud/status"}) response = await client.receive_json() assert response["result"] == { "logged_in": False, "cloud": "disconnected", "http_use_ssl": False, } async def test_websocket_subscription_info( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, aioclient_mock: AiohttpClientMocker, mock_auth, mock_cloud_login, ) -> None: """Test querying the status and connecting because valid account.""" aioclient_mock.get(SUBSCRIPTION_INFO_URL, json={"provider": "stripe"}) client = await hass_ws_client(hass) with patch("hass_nabucasa.auth.CognitoAuth.async_renew_access_token") as mock_renew: await client.send_json({"id": 5, "type": "cloud/subscription"}) response = await client.receive_json() assert response["result"] == {"provider": "stripe"} assert len(mock_renew.mock_calls) == 1 async def test_websocket_subscription_fail( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, aioclient_mock: AiohttpClientMocker, mock_auth, mock_cloud_login, ) -> None: """Test querying the status.""" aioclient_mock.get(SUBSCRIPTION_INFO_URL, status=HTTPStatus.INTERNAL_SERVER_ERROR) client = await hass_ws_client(hass) await client.send_json({"id": 5, "type": "cloud/subscription"}) response = await client.receive_json() assert not response["success"] assert response["error"]["code"] == "request_failed" async def test_websocket_subscription_not_logged_in( hass: HomeAssistant, hass_ws_client: WebSocketGenerator ) -> None: """Test querying the status.""" client = await hass_ws_client(hass) with patch( "hass_nabucasa.cloud_api.async_subscription_info", return_value={"return": "value"}, ): await client.send_json({"id": 5, "type": "cloud/subscription"}) response = await client.receive_json() assert not response["success"] assert response["error"]["code"] == "not_logged_in" async def test_websocket_update_preferences( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, aioclient_mock: AiohttpClientMocker, setup_api, mock_cloud_login, ) -> None: """Test updating preference.""" assert setup_api.google_enabled assert setup_api.alexa_enabled assert setup_api.google_secure_devices_pin is None client = await hass_ws_client(hass) await client.send_json( { "id": 5, "type": "cloud/update_prefs", "alexa_enabled": False, "google_enabled": False, "google_secure_devices_pin": "1234", "google_default_expose": ["light", "switch"], "alexa_default_expose": ["sensor", "media_player"], "tts_default_voice": ["en-GB", "male"], } ) response = await client.receive_json() assert response["success"] assert not setup_api.google_enabled assert not setup_api.alexa_enabled assert setup_api.google_secure_devices_pin == "1234" assert setup_api.google_default_expose == ["light", "switch"] assert setup_api.alexa_default_expose == ["sensor", "media_player"] assert setup_api.tts_default_voice == ("en-GB", "male") async def test_websocket_update_preferences_alexa_report_state( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, aioclient_mock: AiohttpClientMocker, setup_api, mock_cloud_login, ) -> None: """Test updating alexa_report_state sets alexa authorized.""" client = await hass_ws_client(hass) with patch( ( "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" ".async_get_access_token" ), ), patch( "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.set_authorized" ) as set_authorized_mock: set_authorized_mock.assert_not_called() await client.send_json( {"id": 5, "type": "cloud/update_prefs", "alexa_report_state": True} ) response = await client.receive_json() set_authorized_mock.assert_called_once_with(True) assert response["success"] async def test_websocket_update_preferences_require_relink( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, aioclient_mock: AiohttpClientMocker, setup_api, mock_cloud_login, ) -> None: """Test updating preference requires relink.""" client = await hass_ws_client(hass) with patch( ( "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" ".async_get_access_token" ), side_effect=alexa_errors.RequireRelink, ), patch( "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.set_authorized" ) as set_authorized_mock: set_authorized_mock.assert_not_called() await client.send_json( {"id": 5, "type": "cloud/update_prefs", "alexa_report_state": True} ) response = await client.receive_json() set_authorized_mock.assert_called_once_with(False) assert not response["success"] assert response["error"]["code"] == "alexa_relink" async def test_websocket_update_preferences_no_token( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, aioclient_mock: AiohttpClientMocker, setup_api, mock_cloud_login, ) -> None: """Test updating preference no token available.""" client = await hass_ws_client(hass) with patch( ( "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" ".async_get_access_token" ), side_effect=alexa_errors.NoTokenAvailable, ), patch( "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.set_authorized" ) as set_authorized_mock: set_authorized_mock.assert_not_called() await client.send_json( {"id": 5, "type": "cloud/update_prefs", "alexa_report_state": True} ) response = await client.receive_json() set_authorized_mock.assert_called_once_with(False) assert not response["success"] assert response["error"]["code"] == "alexa_relink" async def test_enabling_webhook( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login ) -> None: """Test we call right code to enable webhooks.""" client = await hass_ws_client(hass) with patch( "hass_nabucasa.cloudhooks.Cloudhooks.async_create", return_value={} ) as mock_enable: await client.send_json( {"id": 5, "type": "cloud/cloudhook/create", "webhook_id": "mock-webhook-id"} ) response = await client.receive_json() assert response["success"] assert len(mock_enable.mock_calls) == 1 assert mock_enable.mock_calls[0][1][0] == "mock-webhook-id" async def test_disabling_webhook( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login ) -> None: """Test we call right code to disable webhooks.""" client = await hass_ws_client(hass) with patch("hass_nabucasa.cloudhooks.Cloudhooks.async_delete") as mock_disable: await client.send_json( {"id": 5, "type": "cloud/cloudhook/delete", "webhook_id": "mock-webhook-id"} ) response = await client.receive_json() assert response["success"] assert len(mock_disable.mock_calls) == 1 assert mock_disable.mock_calls[0][1][0] == "mock-webhook-id" async def test_enabling_remote( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login ) -> None: """Test we call right code to enable remote UI.""" client = await hass_ws_client(hass) cloud = hass.data[DOMAIN] with patch("hass_nabucasa.remote.RemoteUI.connect") as mock_connect: await client.send_json({"id": 5, "type": "cloud/remote/connect"}) response = await client.receive_json() assert response["success"] assert cloud.client.remote_autostart assert len(mock_connect.mock_calls) == 1 with patch("hass_nabucasa.remote.RemoteUI.disconnect") as mock_disconnect: await client.send_json({"id": 6, "type": "cloud/remote/disconnect"}) response = await client.receive_json() assert response["success"] assert not cloud.client.remote_autostart assert len(mock_disconnect.mock_calls) == 1 async def test_list_google_entities( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login ) -> None: """Test that we can list Google entities.""" client = await hass_ws_client(hass) entity = GoogleEntity( hass, MockConfig(should_expose=lambda *_: False), State("light.kitchen", "on") ) entity2 = GoogleEntity( hass, MockConfig(should_expose=lambda *_: True, should_2fa=lambda *_: False), State("cover.garage", "open", {"device_class": "garage"}), ) with patch( "homeassistant.components.google_assistant.helpers.async_get_entities", return_value=[entity, entity2], ): await client.send_json({"id": 5, "type": "cloud/google_assistant/entities"}) response = await client.receive_json() assert response["success"] assert len(response["result"]) == 2 assert response["result"][0] == { "entity_id": "light.kitchen", "might_2fa": False, "traits": ["action.devices.traits.OnOff"], } assert response["result"][1] == { "entity_id": "cover.garage", "might_2fa": True, "traits": ["action.devices.traits.OpenClose"], } async def test_update_google_entity( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login ) -> None: """Test that we can update config of a Google entity.""" client = await hass_ws_client(hass) await client.send_json( { "id": 5, "type": "cloud/google_assistant/entities/update", "entity_id": "light.kitchen", "should_expose": False, "override_name": "updated name", "aliases": ["lefty", "righty"], "disable_2fa": False, } ) response = await client.receive_json() assert response["success"] prefs = hass.data[DOMAIN].client.prefs assert prefs.google_entity_configs["light.kitchen"] == { "should_expose": False, "override_name": "updated name", "aliases": ["lefty", "righty"], "disable_2fa": False, } await client.send_json( { "id": 6, "type": "cloud/google_assistant/entities/update", "entity_id": "light.kitchen", "should_expose": None, } ) response = await client.receive_json() assert response["success"] prefs = hass.data[DOMAIN].client.prefs assert prefs.google_entity_configs["light.kitchen"] == { "should_expose": None, "override_name": "updated name", "aliases": ["lefty", "righty"], "disable_2fa": False, } async def test_list_alexa_entities( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login ) -> None: """Test that we can list Alexa entities.""" client = await hass_ws_client(hass) entity = LightCapabilities( hass, MagicMock(entity_config={}), State("light.kitchen", "on") ) with patch( "homeassistant.components.alexa.entities.async_get_entities", return_value=[entity], ): await client.send_json({"id": 5, "type": "cloud/alexa/entities"}) response = await client.receive_json() assert response["success"] assert len(response["result"]) == 1 assert response["result"][0] == { "entity_id": "light.kitchen", "display_categories": ["LIGHT"], "interfaces": ["Alexa.PowerController", "Alexa.EndpointHealth", "Alexa"], } async def test_update_alexa_entity( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login ) -> None: """Test that we can update config of an Alexa entity.""" client = await hass_ws_client(hass) await client.send_json( { "id": 5, "type": "cloud/alexa/entities/update", "entity_id": "light.kitchen", "should_expose": False, } ) response = await client.receive_json() assert response["success"] prefs = hass.data[DOMAIN].client.prefs assert prefs.alexa_entity_configs["light.kitchen"] == {"should_expose": False} await client.send_json( { "id": 6, "type": "cloud/alexa/entities/update", "entity_id": "light.kitchen", "should_expose": None, } ) response = await client.receive_json() assert response["success"] prefs = hass.data[DOMAIN].client.prefs assert prefs.alexa_entity_configs["light.kitchen"] == {"should_expose": None} async def test_sync_alexa_entities_timeout( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login ) -> None: """Test that timeout syncing Alexa entities.""" client = await hass_ws_client(hass) with patch( ( "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" ".async_sync_entities" ), side_effect=asyncio.TimeoutError, ): await client.send_json({"id": 5, "type": "cloud/alexa/sync"}) response = await client.receive_json() assert not response["success"] assert response["error"]["code"] == "timeout" async def test_sync_alexa_entities_no_token( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login ) -> None: """Test sync Alexa entities when we have no token.""" client = await hass_ws_client(hass) with patch( ( "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" ".async_sync_entities" ), side_effect=alexa_errors.NoTokenAvailable, ): await client.send_json({"id": 5, "type": "cloud/alexa/sync"}) response = await client.receive_json() assert not response["success"] assert response["error"]["code"] == "alexa_relink" async def test_enable_alexa_state_report_fail( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login ) -> None: """Test enable Alexa entities state reporting when no token available.""" client = await hass_ws_client(hass) with patch( ( "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" ".async_sync_entities" ), side_effect=alexa_errors.NoTokenAvailable, ): await client.send_json({"id": 5, "type": "cloud/alexa/sync"}) response = await client.receive_json() assert not response["success"] assert response["error"]["code"] == "alexa_relink" async def test_thingtalk_convert( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api ) -> None: """Test that we can convert a query.""" client = await hass_ws_client(hass) with patch( "homeassistant.components.cloud.http_api.thingtalk.async_convert", return_value={"hello": "world"}, ): await client.send_json( {"id": 5, "type": "cloud/thingtalk/convert", "query": "some-data"} ) response = await client.receive_json() assert response["success"] assert response["result"] == {"hello": "world"} async def test_thingtalk_convert_timeout( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api ) -> None: """Test that we can convert a query.""" client = await hass_ws_client(hass) with patch( "homeassistant.components.cloud.http_api.thingtalk.async_convert", side_effect=asyncio.TimeoutError, ): await client.send_json( {"id": 5, "type": "cloud/thingtalk/convert", "query": "some-data"} ) response = await client.receive_json() assert not response["success"] assert response["error"]["code"] == "timeout" async def test_thingtalk_convert_internal( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api ) -> None: """Test that we can convert a query.""" client = await hass_ws_client(hass) with patch( "homeassistant.components.cloud.http_api.thingtalk.async_convert", side_effect=thingtalk.ThingTalkConversionError("Did not understand"), ): await client.send_json( {"id": 5, "type": "cloud/thingtalk/convert", "query": "some-data"} ) response = await client.receive_json() assert not response["success"] assert response["error"]["code"] == "unknown_error" assert response["error"]["message"] == "Did not understand" async def test_tts_info( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api ) -> None: """Test that we can get TTS info.""" # Verify the format is as expected assert voice.MAP_VOICE[("en-US", voice.Gender.FEMALE)] == "JennyNeural" client = await hass_ws_client(hass) with patch.dict( "homeassistant.components.cloud.http_api.MAP_VOICE", { ("en-US", voice.Gender.MALE): "GuyNeural", ("en-US", voice.Gender.FEMALE): "JennyNeural", }, clear=True, ): await client.send_json({"id": 5, "type": "cloud/tts/info"}) response = await client.receive_json() assert response["success"] assert response["result"] == {"languages": [["en-US", "male"], ["en-US", "female"]]}