From 5bb233998e87ffa0acebdea0d39a6f8a715d614e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 13 Dec 2023 13:53:22 +0100 Subject: [PATCH] Improve cloud http api tests (#105610) * Improve cloud http api tests * Add comments to the cloud fixture * Fix docstring --- tests/components/cloud/conftest.py | 114 +++- tests/components/cloud/test_http_api.py | 726 ++++++++++++++++-------- 2 files changed, 599 insertions(+), 241 deletions(-) diff --git a/tests/components/cloud/conftest.py b/tests/components/cloud/conftest.py index 221267c59fb..0de43c80e87 100644 --- a/tests/components/cloud/conftest.py +++ b/tests/components/cloud/conftest.py @@ -1,14 +1,124 @@ """Fixtures for cloud tests.""" -from unittest.mock import patch +from collections.abc import AsyncGenerator +from typing import Any +from unittest.mock import DEFAULT, MagicMock, PropertyMock, patch +from hass_nabucasa import Cloud +from hass_nabucasa.auth import CognitoAuth +from hass_nabucasa.cloudhooks import Cloudhooks +from hass_nabucasa.const import DEFAULT_SERVERS, DEFAULT_VALUES, STATE_CONNECTED +from hass_nabucasa.google_report_state import GoogleReportState +from hass_nabucasa.iot import CloudIoT +from hass_nabucasa.remote import RemoteUI +from hass_nabucasa.voice import Voice import jwt import pytest -from homeassistant.components.cloud import const, prefs +from homeassistant.components.cloud import CloudClient, const, prefs from . import mock_cloud, mock_cloud_prefs +@pytest.fixture(name="cloud") +async def cloud_fixture() -> AsyncGenerator[MagicMock, None]: + """Mock the cloud object. + + See the real hass_nabucasa.Cloud class for how to configure the mock. + """ + with patch( + "homeassistant.components.cloud.Cloud", autospec=True + ) as mock_cloud_class: + mock_cloud = mock_cloud_class.return_value + + # Attributes set in the constructor without parameters. + # We spec the mocks with the real classes + # and set constructor attributes or mock properties as needed. + mock_cloud.google_report_state = MagicMock(spec=GoogleReportState) + mock_cloud.cloudhooks = MagicMock(spec=Cloudhooks) + mock_cloud.remote = MagicMock( + spec=RemoteUI, + certificate=None, + certificate_status=None, + instance_domain=None, + is_connected=False, + ) + mock_cloud.auth = MagicMock(spec=CognitoAuth) + mock_cloud.iot = MagicMock( + spec=CloudIoT, last_disconnect_reason=None, state=STATE_CONNECTED + ) + mock_cloud.voice = MagicMock(spec=Voice) + mock_cloud.started = None + + def set_up_mock_cloud( + cloud_client: CloudClient, mode: str, **kwargs: Any + ) -> DEFAULT: + """Set up mock cloud with a mock constructor.""" + + # Attributes set in the constructor with parameters. + cloud_client.cloud = mock_cloud + mock_cloud.client = cloud_client + default_values = DEFAULT_VALUES[mode] + servers = { + f"{name}_server": server + for name, server in DEFAULT_SERVERS[mode].items() + } + mock_cloud.configure_mock(**default_values, **servers, **kwargs) + mock_cloud.mode = mode + + # Properties that we mock as attributes from the constructor. + mock_cloud.websession = cloud_client.websession + + return DEFAULT + + mock_cloud_class.side_effect = set_up_mock_cloud + + # Attributes that we mock with default values. + + mock_cloud.id_token = jwt.encode( + { + "email": "hello@home-assistant.io", + "custom:sub-exp": "2018-01-03", + "cognito:username": "abcdefghjkl", + }, + "test", + ) + mock_cloud.access_token = "test_access_token" + mock_cloud.refresh_token = "test_refresh_token" + + # Properties that we keep as properties. + + def mock_is_logged_in() -> bool: + """Mock is logged in.""" + return mock_cloud.id_token is not None + + is_logged_in = PropertyMock(side_effect=mock_is_logged_in) + type(mock_cloud).is_logged_in = is_logged_in + + def mock_claims() -> dict[str, Any]: + """Mock claims.""" + return Cloud._decode_claims(mock_cloud.id_token) + + claims = PropertyMock(side_effect=mock_claims) + type(mock_cloud).claims = claims + + # Properties that we mock as attributes. + mock_cloud.subscription_expired = False + + # Methods that we mock with a custom side effect. + + async def mock_login(email: str, password: str) -> None: + """Mock login. + + When called, it should call the on_start callback. + """ + on_start_callback = mock_cloud.register_on_start.call_args[0][0] + await on_start_callback() + + mock_cloud.login.side_effect = mock_login + + yield mock_cloud + + @pytest.fixture(autouse=True) def mock_tts_cache_dir_autouse(mock_tts_cache_dir): """Mock the TTS cache dir with empty dir.""" diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index fc6861f2b49..15acc275931 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -1,19 +1,21 @@ """Tests for the HTTP API for the cloud component.""" import asyncio +from copy import deepcopy from http import HTTPStatus from typing import Any from unittest.mock import AsyncMock, MagicMock, Mock, patch import aiohttp +from aiohttp.test_utils import TestClient 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.assist_pipeline.pipeline import STORAGE_KEY +from homeassistant.components.cloud.const import DEFAULT_EXPOSED_DOMAINS, DOMAIN from homeassistant.components.google_assistant.helpers import GoogleEntity from homeassistant.components.homeassistant import exposed_entities from homeassistant.core import HomeAssistant, State @@ -21,39 +23,67 @@ from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component 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 ClientSessionGenerator, WebSocketGenerator +PIPELINE_DATA_LEGACY = { + "items": [ + { + "conversation_engine": "homeassistant", + "conversation_language": "language_1", + "id": "12345", + "language": "language_1", + "name": "Home Assistant Cloud", + "stt_engine": "cloud", + "stt_language": "language_1", + "tts_engine": "cloud", + "tts_language": "language_1", + "tts_voice": "Arnold Schwarzenegger", + "wake_word_entity": None, + "wake_word_id": None, + }, + ], + "preferred_item": "12345", +} + +PIPELINE_DATA_OTHER = { + "items": [ + { + "conversation_engine": "other", + "conversation_language": "language_1", + "id": "12345", + "language": "language_1", + "name": "Home Assistant", + "stt_engine": "stt.other", + "stt_language": "language_1", + "tts_engine": "other", + "tts_language": "language_1", + "tts_voice": "Arnold Schwarzenegger", + "wake_word_entity": None, + "wake_word_id": None, + }, + ], + "preferred_item": "12345", +} + 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( +@pytest.fixture(name="setup_cloud") +async def setup_cloud_fixture(hass: HomeAssistant, cloud: MagicMock) -> None: + """Fixture that sets up cloud.""" + assert await async_setup_component(hass, "homeassistant", {}) + assert await async_setup_component( + hass, + DOMAIN, { - "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, - { + DOMAIN: { "mode": "development", "cognito_client_id": "cognito_client_id", "user_pool_id": "user_pool_id", "region": "region", + "alexa_server": "alexa-api.nabucasa.com", "relayer_server": "relayer", "accounts_server": "api-test.hass.io", "google_actions": {"filter": {"include_domains": "light"}}, @@ -61,27 +91,24 @@ def setup_api_fixture(hass, aioclient_mock): "filter": {"include_entities": ["light.kitchen", "switch.ac"]} }, }, - ) + }, ) - return mock_cloud_prefs(hass) + await hass.async_block_till_done() + on_start_callback = cloud.register_on_start.call_args[0][0] + await on_start_callback() @pytest.fixture(name="cloud_client") -def cloud_client_fixture(hass, hass_client): +async def cloud_client_fixture( + hass: HomeAssistant, hass_client: ClientSessionGenerator +) -> TestClient: """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() + return await hass_client() async def test_google_actions_sync( - mock_cognito, mock_cloud_login, cloud_client + setup_cloud: None, + cloud_client: TestClient, ) -> None: """Test syncing Google Actions.""" with patch( @@ -90,11 +117,12 @@ async def test_google_actions_sync( ) 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 + assert mock_request_sync.call_count == 1 async def test_google_actions_sync_fails( - mock_cognito, mock_cloud_login, cloud_client + setup_cloud: None, + cloud_client: TestClient, ) -> None: """Test syncing Google Actions gone bad.""" with patch( @@ -103,26 +131,32 @@ async def test_google_actions_sync_fails( ) 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 + assert mock_request_sync.call_count == 1 -async def test_login_view(hass: HomeAssistant, cloud_client) -> None: +@pytest.mark.parametrize("pipeline_data", [PIPELINE_DATA_LEGACY]) +async def test_login_view_existing_pipeline( + hass: HomeAssistant, + cloud: MagicMock, + hass_client: ClientSessionGenerator, + hass_storage: dict[str, Any], + pipeline_data: dict[str, Any], +) -> None: """Test logging in when an assist pipeline is available.""" - hass.data["cloud"] = MagicMock(login=AsyncMock()) - await async_setup_component(hass, "stt", {}) - await async_setup_component(hass, "tts", {}) + hass_storage[STORAGE_KEY] = { + "version": 1, + "minor_version": 1, + "key": STORAGE_KEY, + "data": deepcopy(pipeline_data), + } + + assert await async_setup_component(hass, "homeassistant", {}) + assert await async_setup_component(hass, DOMAIN, {"cloud": {}}) + await hass.async_block_till_done() + + cloud_client = await hass_client() with patch( - "homeassistant.components.cloud.http_api.assist_pipeline.async_get_pipelines", - return_value=[ - Mock( - conversation_engine="homeassistant", - id="12345", - stt_engine=DOMAIN, - tts_engine=DOMAIN, - ) - ], - ), patch( "homeassistant.components.cloud.http_api.assist_pipeline.async_create_default_pipeline", ) as create_pipeline_mock: req = await cloud_client.post( @@ -135,11 +169,25 @@ async def test_login_view(hass: HomeAssistant, cloud_client) -> None: create_pipeline_mock.assert_not_awaited() -async def test_login_view_create_pipeline(hass: HomeAssistant, cloud_client) -> None: - """Test logging in when no assist pipeline is available.""" - hass.data["cloud"] = MagicMock(login=AsyncMock()) - await async_setup_component(hass, "stt", {}) - await async_setup_component(hass, "tts", {}) +async def test_login_view_create_pipeline( + hass: HomeAssistant, + cloud: MagicMock, + hass_client: ClientSessionGenerator, + hass_storage: dict[str, Any], +) -> None: + """Test logging in when no existing cloud assist pipeline is available.""" + hass_storage[STORAGE_KEY] = { + "version": 1, + "minor_version": 1, + "key": STORAGE_KEY, + "data": deepcopy(PIPELINE_DATA_OTHER), + } + + assert await async_setup_component(hass, "homeassistant", {}) + assert await async_setup_component(hass, DOMAIN, {"cloud": {}}) + await hass.async_block_till_done() + + cloud_client = await hass_client() with patch( "homeassistant.components.cloud.http_api.assist_pipeline.async_create_default_pipeline", @@ -156,12 +204,24 @@ async def test_login_view_create_pipeline(hass: HomeAssistant, cloud_client) -> async def test_login_view_create_pipeline_fail( - hass: HomeAssistant, cloud_client + hass: HomeAssistant, + cloud: MagicMock, + hass_client: ClientSessionGenerator, + hass_storage: dict[str, Any], ) -> None: """Test logging in when no assist pipeline is available.""" - hass.data["cloud"] = MagicMock(login=AsyncMock()) - await async_setup_component(hass, "stt", {}) - await async_setup_component(hass, "tts", {}) + hass_storage[STORAGE_KEY] = { + "version": 1, + "minor_version": 1, + "key": STORAGE_KEY, + "data": deepcopy(PIPELINE_DATA_OTHER), + } + + assert await async_setup_component(hass, "homeassistant", {}) + assert await async_setup_component(hass, DOMAIN, {"cloud": {}}) + await hass.async_block_till_done() + + cloud_client = await hass_client() with patch( "homeassistant.components.cloud.http_api.assist_pipeline.async_create_default_pipeline", @@ -177,96 +237,143 @@ async def test_login_view_create_pipeline_fail( create_pipeline_mock.assert_awaited_once_with(hass, "cloud", "cloud") -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"} - ) +async def test_login_view_random_exception( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Try logging in with random exception.""" + 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: +async def test_login_view_invalid_json( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> 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") + mock_login = cloud.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 + assert mock_login.call_count == 0 -async def test_login_view_invalid_schema(cloud_client) -> None: +async def test_login_view_invalid_schema( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> 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"}) + mock_login = cloud.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 + assert mock_login.call_count == 0 -async def test_login_view_request_timeout(cloud_client) -> None: +async def test_login_view_request_timeout( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> 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"} - ) + cloud.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: +async def test_login_view_invalid_credentials( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> 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"} - ) + cloud.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: +async def test_login_view_unknown_error( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> 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"} - ) + cloud.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: +async def test_logout_view( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> 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 + assert cloud.logout.call_count == 1 -async def test_logout_view_request_timeout(hass: HomeAssistant, cloud_client) -> None: +async def test_logout_view_request_timeout( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> 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: +async def test_logout_view_unknown_error( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> 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: +async def test_register_view_no_location( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: """Test register without location.""" + mock_cognito = cloud.auth with patch( "homeassistant.components.cloud.http_api.async_detect_location_info", return_value=None, @@ -275,17 +382,23 @@ async def test_register_view_no_location(mock_cognito, cloud_client) -> None: "/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] + assert mock_cognito.async_register.call_count == 1 + call = mock_cognito.async_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: +async def test_register_view_with_location( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: """Test register with location.""" + mock_cognito = cloud.auth with patch( "homeassistant.components.cloud.http_api.async_detect_location_info", return_value=LocationInfo( @@ -308,9 +421,10 @@ async def test_register_view_with_location(mock_cognito, cloud_client) -> None: "/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] + assert mock_cognito.async_register.call_count == 1 + call = mock_cognito.async_register.mock_calls[0] result_email, result_pass = call.args assert result_email == "hello@bla.com" assert result_pass == "falcon42" @@ -321,124 +435,201 @@ async def test_register_view_with_location(mock_cognito, cloud_client) -> None: } -async def test_register_view_bad_data(mock_cognito, cloud_client) -> None: - """Test logging out.""" +async def test_register_view_bad_data( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Test register bad data.""" + mock_cognito = cloud.auth + 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 + assert mock_cognito.async_register.call_count == 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 +async def test_register_view_request_timeout( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Test timeout while registering.""" + cloud.auth.async_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 +async def test_register_view_unknown_error( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Test unknown error while registering.""" + cloud.auth.async_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.""" +async def test_forgot_password_view( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Test forgot password.""" + mock_cognito = cloud.auth + 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 + assert mock_cognito.async_forgot_password.call_count == 1 -async def test_forgot_password_view_bad_data(mock_cognito, cloud_client) -> None: - """Test logging out.""" +async def test_forgot_password_view_bad_data( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Test forgot password bad data.""" + mock_cognito = cloud.auth + 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 + assert mock_cognito.async_forgot_password.call_count == 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 +async def test_forgot_password_view_request_timeout( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Test timeout while forgot password.""" + cloud.auth.async_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 +async def test_forgot_password_view_unknown_error( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Test unknown error while forgot password.""" + cloud.auth.async_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( +async def test_forgot_password_view_aiohttp_error( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Test unknown error while forgot password.""" + cloud.auth.async_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.""" +async def test_resend_confirm_view( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Test resend confirm.""" + mock_cognito = cloud.auth + 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 + assert mock_cognito.async_resend_email_confirm.call_count == 1 -async def test_resend_confirm_view_bad_data(mock_cognito, cloud_client) -> None: - """Test logging out.""" +async def test_resend_confirm_view_bad_data( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Test resend confirm bad data.""" + mock_cognito = cloud.auth + 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 + assert mock_cognito.async_resend_email_confirm.call_count == 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 +async def test_resend_confirm_view_request_timeout( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Test timeout while resend confirm.""" + cloud.auth.async_resend_email_confirm.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 +async def test_resend_confirm_view_unknown_error( + cloud: MagicMock, + setup_cloud: None, + cloud_client: TestClient, +) -> None: + """Test unknown error while resend confirm.""" + cloud.auth.async_resend_email_confirm.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, + cloud: MagicMock, + setup_cloud: None, ) -> None: """Test querying the status.""" - hass.data[DOMAIN].iot.state = STATE_CONNECTED + cloud.iot.state = STATE_CONNECTED client = await hass_ws_client(hass) with patch.dict( @@ -452,6 +643,7 @@ async def test_websocket_status( ): 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", @@ -462,8 +654,8 @@ async def test_websocket_status( "cloudhooks": {}, "google_enabled": True, "google_secure_devices_pin": None, - "google_default_expose": None, - "alexa_default_expose": None, + "google_default_expose": DEFAULT_EXPOSED_DOMAINS, + "alexa_default_expose": DEFAULT_EXPOSED_DOMAINS, "alexa_report_state": True, "google_report_state": True, "remote_enabled": False, @@ -493,17 +685,23 @@ async def test_websocket_status( "remote_certificate_status": None, "remote_certificate": None, "http_use_ssl": False, - "active_subscription": False, + "active_subscription": True, } async def test_websocket_status_not_logged_in( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + cloud: MagicMock, + setup_cloud: None, ) -> None: - """Test querying the status.""" + """Test querying the status not logged in.""" + cloud.id_token = None 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", @@ -515,30 +713,32 @@ async def test_websocket_subscription_info( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, aioclient_mock: AiohttpClientMocker, - mock_auth, - mock_cloud_login, + cloud: MagicMock, + setup_cloud: None, ) -> None: - """Test querying the status and connecting because valid account.""" + """Test subscription info and connecting because valid account.""" aioclient_mock.get(SUBSCRIPTION_INFO_URL, json={"provider": "stripe"}) client = await hass_ws_client(hass) + mock_renew = cloud.auth.async_renew_access_token + + await client.send_json({"id": 5, "type": "cloud/subscription"}) + response = await client.receive_json() - 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 + assert mock_renew.call_count == 1 async def test_websocket_subscription_fail( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, aioclient_mock: AiohttpClientMocker, - mock_auth, - mock_cloud_login, + cloud: MagicMock, + setup_cloud: None, ) -> None: - """Test querying the status.""" + """Test subscription info fail.""" 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() @@ -547,10 +747,15 @@ async def test_websocket_subscription_fail( async def test_websocket_subscription_not_logged_in( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + cloud: MagicMock, + setup_cloud: None, ) -> None: - """Test querying the status.""" + """Test subscription info not logged in.""" + cloud.id_token = None client = await hass_ws_client(hass) + with patch( "hass_nabucasa.cloud_api.async_subscription_info", return_value={"return": "value"}, @@ -565,15 +770,16 @@ async def test_websocket_subscription_not_logged_in( async def test_websocket_update_preferences( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, - aioclient_mock: AiohttpClientMocker, - setup_api, - mock_cloud_login, + cloud: MagicMock, + setup_cloud: None, ) -> None: """Test updating preference.""" - assert setup_api.google_enabled - assert setup_api.alexa_enabled - assert setup_api.google_secure_devices_pin is None + assert cloud.client.prefs.google_enabled + assert cloud.client.prefs.alexa_enabled + assert cloud.client.prefs.google_secure_devices_pin is None + client = await hass_ws_client(hass) + await client.send_json( { "id": 5, @@ -587,18 +793,16 @@ async def test_websocket_update_preferences( 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.tts_default_voice == ("en-GB", "male") + assert not cloud.client.prefs.google_enabled + assert not cloud.client.prefs.alexa_enabled + assert cloud.client.prefs.google_secure_devices_pin == "1234" + assert cloud.client.prefs.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, + setup_cloud: None, ) -> None: """Test updating alexa_report_state sets alexa authorized.""" client = await hass_ws_client(hass) @@ -612,10 +816,12 @@ async def test_websocket_update_preferences_alexa_report_state( "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"] @@ -624,9 +830,7 @@ async def test_websocket_update_preferences_alexa_report_state( async def test_websocket_update_preferences_require_relink( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, - aioclient_mock: AiohttpClientMocker, - setup_api, - mock_cloud_login, + setup_cloud: None, ) -> None: """Test updating preference requires relink.""" client = await hass_ws_client(hass) @@ -641,10 +845,12 @@ async def test_websocket_update_preferences_require_relink( "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"] @@ -654,9 +860,7 @@ async def test_websocket_update_preferences_require_relink( async def test_websocket_update_preferences_no_token( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, - aioclient_mock: AiohttpClientMocker, - setup_api, - mock_cloud_login, + setup_cloud: None, ) -> None: """Test updating preference no token available.""" client = await hass_ws_client(hass) @@ -671,10 +875,12 @@ async def test_websocket_update_preferences_no_token( "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"] @@ -682,69 +888,79 @@ async def test_websocket_update_preferences_no_token( async def test_enabling_webhook( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + cloud: MagicMock, + setup_cloud: None, ) -> 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"] + mock_enable = cloud.cloudhooks.async_create + mock_enable.return_value = {} - assert len(mock_enable.mock_calls) == 1 + await client.send_json( + {"id": 5, "type": "cloud/cloudhook/create", "webhook_id": "mock-webhook-id"} + ) + response = await client.receive_json() + + assert response["success"] + assert mock_enable.call_count == 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 + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + cloud: MagicMock, + setup_cloud: None, ) -> 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"] + mock_disable = cloud.cloudhooks.async_delete - assert len(mock_disable.mock_calls) == 1 + await client.send_json( + {"id": 5, "type": "cloud/cloudhook/delete", "webhook_id": "mock-webhook-id"} + ) + response = await client.receive_json() + + assert response["success"] + assert mock_disable.call_count == 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 + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + cloud: MagicMock, + setup_cloud: None, ) -> 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"] + mock_connect = cloud.remote.connect assert not cloud.client.remote_autostart - assert len(mock_disconnect.mock_calls) == 1 + await client.send_json({"id": 5, "type": "cloud/remote/connect"}) + response = await client.receive_json() + + assert response["success"] + assert cloud.client.remote_autostart + assert mock_connect.call_count == 1 + + mock_disconnect = cloud.remote.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 mock_disconnect.call_count == 1 async def test_list_google_entities( hass: HomeAssistant, entity_registry: er.EntityRegistry, hass_ws_client: WebSocketGenerator, - setup_api, - mock_cloud_login, + setup_cloud: None, ) -> None: """Test that we can list Google entities.""" client = await hass_ws_client(hass) @@ -762,6 +978,7 @@ async def test_list_google_entities( ): await client.send_json_auto_id({"type": "cloud/google_assistant/entities"}) response = await client.receive_json() + assert response["success"] assert len(response["result"]) == 2 assert response["result"][0] == { @@ -789,6 +1006,7 @@ async def test_list_google_entities( ): await client.send_json_auto_id({"type": "cloud/google_assistant/entities"}) response = await client.receive_json() + assert response["success"] assert len(response["result"]) == 2 assert response["result"][0] == { @@ -807,8 +1025,7 @@ async def test_get_google_entity( hass: HomeAssistant, entity_registry: er.EntityRegistry, hass_ws_client: WebSocketGenerator, - setup_api, - mock_cloud_login, + setup_cloud: None, ) -> None: """Test that we can get a Google entity.""" client = await hass_ws_client(hass) @@ -818,6 +1035,7 @@ async def test_get_google_entity( {"type": "cloud/google_assistant/entities/get", "entity_id": "light.kitchen"} ) response = await client.receive_json() + assert not response["success"] assert response["error"] == { "code": "not_found", @@ -829,10 +1047,12 @@ async def test_get_google_entity( "group", "test", "unique", suggested_object_id="all_locks" ) hass.states.async_set("group.all_locks", "bla") + await client.send_json_auto_id( {"type": "cloud/google_assistant/entities/get", "entity_id": "group.all_locks"} ) response = await client.receive_json() + assert not response["success"] assert response["error"] == { "code": "not_supported", @@ -849,6 +1069,7 @@ async def test_get_google_entity( {"type": "cloud/google_assistant/entities/get", "entity_id": "light.kitchen"} ) response = await client.receive_json() + assert response["success"] assert response["result"] == { "disable_2fa": None, @@ -861,6 +1082,7 @@ async def test_get_google_entity( {"type": "cloud/google_assistant/entities/get", "entity_id": "cover.garage"} ) response = await client.receive_json() + assert response["success"] assert response["result"] == { "disable_2fa": None, @@ -878,12 +1100,14 @@ async def test_get_google_entity( } ) response = await client.receive_json() + assert response["success"] await client.send_json_auto_id( {"type": "cloud/google_assistant/entities/get", "entity_id": "cover.garage"} ) response = await client.receive_json() + assert response["success"] assert response["result"] == { "disable_2fa": True, @@ -895,13 +1119,12 @@ async def test_get_google_entity( async def test_update_google_entity( hass: HomeAssistant, - entity_registry: er.EntityRegistry, hass_ws_client: WebSocketGenerator, - setup_api, - mock_cloud_login, + setup_cloud: None, ) -> None: """Test that we can update config of a Google entity.""" client = await hass_ws_client(hass) + await client.send_json_auto_id( { "type": "cloud/google_assistant/entities/update", @@ -910,6 +1133,7 @@ async def test_update_google_entity( } ) response = await client.receive_json() + assert response["success"] await client.send_json_auto_id( @@ -921,8 +1145,8 @@ async def test_update_google_entity( } ) response = await client.receive_json() - assert response["success"] + assert response["success"] assert exposed_entities.async_get_entity_settings(hass, "light.kitchen") == { "cloud.google_assistant": {"disable_2fa": False, "should_expose": False} } @@ -932,8 +1156,7 @@ async def test_list_alexa_entities( hass: HomeAssistant, entity_registry: er.EntityRegistry, hass_ws_client: WebSocketGenerator, - setup_api, - mock_cloud_login, + setup_cloud: None, ) -> None: """Test that we can list Alexa entities.""" client = await hass_ws_client(hass) @@ -946,6 +1169,7 @@ async def test_list_alexa_entities( ): await client.send_json_auto_id({"id": 5, "type": "cloud/alexa/entities"}) response = await client.receive_json() + assert response["success"] assert len(response["result"]) == 1 assert response["result"][0] == { @@ -965,6 +1189,7 @@ async def test_list_alexa_entities( ): await client.send_json_auto_id({"type": "cloud/alexa/entities"}) response = await client.receive_json() + assert response["success"] assert len(response["result"]) == 1 assert response["result"][0] == { @@ -978,8 +1203,7 @@ async def test_get_alexa_entity( hass: HomeAssistant, entity_registry: er.EntityRegistry, hass_ws_client: WebSocketGenerator, - setup_api, - mock_cloud_login, + setup_cloud: None, ) -> None: """Test that we can get an Alexa entity.""" client = await hass_ws_client(hass) @@ -989,6 +1213,7 @@ async def test_get_alexa_entity( {"type": "cloud/alexa/entities/get", "entity_id": "light.kitchen"} ) response = await client.receive_json() + assert response["success"] assert response["result"] is None @@ -997,6 +1222,7 @@ async def test_get_alexa_entity( {"type": "cloud/alexa/entities/get", "entity_id": "sensor.temperature"} ) response = await client.receive_json() + assert not response["success"] assert response["error"] == { "code": "not_supported", @@ -1008,10 +1234,12 @@ async def test_get_alexa_entity( "group", "test", "unique", suggested_object_id="all_locks" ) hass.states.async_set("group.all_locks", "bla") + await client.send_json_auto_id( {"type": "cloud/alexa/entities/get", "entity_id": "group.all_locks"} ) response = await client.receive_json() + assert not response["success"] assert response["error"] == { "code": "not_supported", @@ -1029,6 +1257,7 @@ async def test_get_alexa_entity( {"type": "cloud/alexa/entities/get", "entity_id": "light.kitchen"} ) response = await client.receive_json() + assert response["success"] assert response["result"] is None @@ -1036,6 +1265,7 @@ async def test_get_alexa_entity( {"type": "cloud/alexa/entities/get", "entity_id": "water_heater.basement"} ) response = await client.receive_json() + assert not response["success"] assert response["error"] == { "code": "not_supported", @@ -1047,14 +1277,14 @@ async def test_update_alexa_entity( hass: HomeAssistant, entity_registry: er.EntityRegistry, hass_ws_client: WebSocketGenerator, - setup_api, - mock_cloud_login, + setup_cloud: None, ) -> None: """Test that we can update config of an Alexa entity.""" entry = entity_registry.async_get_or_create( "light", "test", "unique", suggested_object_id="kitchen" ) client = await hass_ws_client(hass) + await client.send_json_auto_id( { "type": "homeassistant/expose_entity", @@ -1072,10 +1302,13 @@ async def test_update_alexa_entity( async def test_sync_alexa_entities_timeout( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + setup_cloud: None, ) -> None: """Test that timeout syncing Alexa entities.""" client = await hass_ws_client(hass) + with patch( ( "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" @@ -1091,10 +1324,13 @@ async def test_sync_alexa_entities_timeout( async def test_sync_alexa_entities_no_token( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + setup_cloud: None, ) -> None: """Test sync Alexa entities when we have no token.""" client = await hass_ws_client(hass) + with patch( ( "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" @@ -1110,10 +1346,13 @@ async def test_sync_alexa_entities_no_token( async def test_enable_alexa_state_report_fail( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api, mock_cloud_login + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + setup_cloud: None, ) -> 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" @@ -1129,7 +1368,9 @@ async def test_enable_alexa_state_report_fail( async def test_thingtalk_convert( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + setup_cloud: None, ) -> None: """Test that we can convert a query.""" client = await hass_ws_client(hass) @@ -1148,7 +1389,9 @@ async def test_thingtalk_convert( async def test_thingtalk_convert_timeout( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + setup_cloud: None, ) -> None: """Test that we can convert a query.""" client = await hass_ws_client(hass) @@ -1167,7 +1410,9 @@ async def test_thingtalk_convert_timeout( async def test_thingtalk_convert_internal( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + setup_cloud: None, ) -> None: """Test that we can convert a query.""" client = await hass_ws_client(hass) @@ -1187,7 +1432,9 @@ async def test_thingtalk_convert_internal( async def test_tts_info( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, setup_api + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + setup_cloud: None, ) -> None: """Test that we can get TTS info.""" # Verify the format is as expected @@ -1223,6 +1470,7 @@ async def test_tts_info( ) async def test_api_calls_require_admin( hass: HomeAssistant, + setup_cloud: None, hass_client: ClientSessionGenerator, hass_read_only_access_token: str, endpoint: str,