961 lines
33 KiB
Python
961 lines
33 KiB
Python
"""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"]]}
|