mirror of
https://github.com/home-assistant/core.git
synced 2025-07-31 01:07:10 +00:00
Compare commits
8 Commits
60293648dc
...
c3037bae39
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c3037bae39 | ||
![]() |
9b1ab34352 | ||
![]() |
221a8597da | ||
![]() |
45022752a0 | ||
![]() |
aa342eb476 | ||
![]() |
32b26b8270 | ||
![]() |
e07c29caad | ||
![]() |
b487c12ab1 |
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from aioesphomeapi import APIClient
|
||||
|
||||
from homeassistant.components import ffmpeg, zeroconf
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.components.bluetooth import async_remove_scanner
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
@ -17,13 +17,10 @@ from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.issue_registry import async_delete_issue
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import CONF_BLUETOOTH_MAC_ADDRESS, CONF_NOISE_PSK, DATA_FFMPEG_PROXY, DOMAIN
|
||||
from .dashboard import async_setup as async_setup_dashboard
|
||||
from . import dashboard, ffmpeg_proxy
|
||||
from .const import CONF_BLUETOOTH_MAC_ADDRESS, CONF_NOISE_PSK, DOMAIN
|
||||
from .domain_data import DomainData
|
||||
|
||||
# Import config flow so that it's added to the registry
|
||||
from .entry_data import ESPHomeConfigEntry, RuntimeEntryData
|
||||
from .ffmpeg_proxy import FFmpegProxyData, FFmpegProxyView
|
||||
from .manager import DEVICE_CONFLICT_ISSUE_FORMAT, ESPHomeManager, cleanup_instance
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
@ -33,12 +30,8 @@ CLIENT_INFO = f"Home Assistant {ha_version}"
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the esphome component."""
|
||||
proxy_data = hass.data[DATA_FFMPEG_PROXY] = FFmpegProxyData()
|
||||
|
||||
await async_setup_dashboard(hass)
|
||||
hass.http.register_view(
|
||||
FFmpegProxyView(ffmpeg.get_ffmpeg_manager(hass), proxy_data)
|
||||
)
|
||||
ffmpeg_proxy.async_setup(hass)
|
||||
await dashboard.async_setup(hass)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -47,6 +47,7 @@ from .const import (
|
||||
DOMAIN,
|
||||
)
|
||||
from .dashboard import async_get_or_create_dashboard_manager, async_set_dashboard_info
|
||||
from .entry_data import ESPHomeConfigEntry
|
||||
from .manager import async_replace_device
|
||||
|
||||
ERROR_REQUIRES_ENCRYPTION_KEY = "requires_encryption_key"
|
||||
@ -608,7 +609,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: ESPHomeConfigEntry,
|
||||
) -> OptionsFlowHandler:
|
||||
"""Get the options flow for this handler."""
|
||||
return OptionsFlowHandler()
|
||||
|
@ -22,5 +22,3 @@ PROJECT_URLS = {
|
||||
# ESPHome always uses .0 for the changelog URL
|
||||
STABLE_BLE_URL_VERSION = f"{STABLE_BLE_VERSION.major}.{STABLE_BLE_VERSION.minor}.0"
|
||||
DEFAULT_URL = f"https://esphome.io/changelog/{STABLE_BLE_URL_VERSION}.html"
|
||||
|
||||
DATA_FFMPEG_PROXY = f"{DOMAIN}.ffmpeg_proxy"
|
||||
|
@ -28,6 +28,8 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
# Import config flow so that it's added to the registry
|
||||
from .entry_data import ESPHomeConfigEntry, RuntimeEntryData
|
||||
from .enum_mapper import EsphomeEnumMapper
|
||||
@ -167,7 +169,12 @@ def convert_api_error_ha_error[**_P, _R, _EntityT: EsphomeEntity[Any, Any]](
|
||||
return await func(self, *args, **kwargs)
|
||||
except APIConnectionError as error:
|
||||
raise HomeAssistantError(
|
||||
f"Error communicating with device: {error}"
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="error_communicating_with_device",
|
||||
translation_placeholders={
|
||||
"device_name": self._device_info.name,
|
||||
"error": str(error),
|
||||
},
|
||||
) from error
|
||||
|
||||
return handler
|
||||
@ -194,6 +201,7 @@ class EsphomeEntity(Entity, Generic[_InfoT, _StateT]):
|
||||
_static_info: _InfoT
|
||||
_state: _StateT
|
||||
_has_state: bool
|
||||
device_entry: dr.DeviceEntry
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -11,17 +11,20 @@ from typing import Final
|
||||
from aiohttp import web
|
||||
from aiohttp.abc import AbstractStreamWriter, BaseRequest
|
||||
|
||||
from homeassistant.components import ffmpeg
|
||||
from homeassistant.components.ffmpeg import FFmpegManager
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .const import DATA_FFMPEG_PROXY
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_MAX_CONVERSIONS_PER_DEVICE: Final[int] = 2
|
||||
|
||||
|
||||
@callback
|
||||
def async_create_proxy_url(
|
||||
hass: HomeAssistant,
|
||||
device_id: str,
|
||||
@ -32,7 +35,7 @@ def async_create_proxy_url(
|
||||
width: int | None = None,
|
||||
) -> str:
|
||||
"""Create a use proxy URL that automatically converts the media."""
|
||||
data: FFmpegProxyData = hass.data[DATA_FFMPEG_PROXY]
|
||||
data = hass.data[DATA_FFMPEG_PROXY]
|
||||
return data.async_create_proxy_url(
|
||||
device_id, media_url, media_format, rate, channels, width
|
||||
)
|
||||
@ -313,3 +316,16 @@ class FFmpegProxyView(HomeAssistantView):
|
||||
assert writer is not None
|
||||
await resp.transcode(request, writer)
|
||||
return resp
|
||||
|
||||
|
||||
DATA_FFMPEG_PROXY: HassKey[FFmpegProxyData] = HassKey(f"{DOMAIN}.ffmpeg_proxy")
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup(hass: HomeAssistant) -> None:
|
||||
"""Set up the ffmpeg proxy."""
|
||||
proxy_data = FFmpegProxyData()
|
||||
hass.data[DATA_FFMPEG_PROXY] = proxy_data
|
||||
hass.http.register_view(
|
||||
FFmpegProxyView(ffmpeg.get_ffmpeg_manager(hass), proxy_data)
|
||||
)
|
||||
|
20
homeassistant/components/esphome/icons.json
Normal file
20
homeassistant/components/esphome/icons.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"assist_in_progress": {
|
||||
"default": "mdi:timer-sand"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"pipeline": {
|
||||
"default": "mdi:filter-outline"
|
||||
},
|
||||
"vad_sensitivity": {
|
||||
"default": "mdi:volume-high"
|
||||
},
|
||||
"wake_word": {
|
||||
"default": "mdi:microphone"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -148,10 +148,6 @@ class EsphomeMediaPlayer(
|
||||
announcement: bool,
|
||||
) -> str | None:
|
||||
"""Get URL for ffmpeg proxy."""
|
||||
if self.device_entry is None:
|
||||
# Device id is required
|
||||
return None
|
||||
|
||||
# Choose the first default or announcement supported format
|
||||
format_to_use: MediaPlayerSupportedFormat | None = None
|
||||
for supported_format in supported_formats:
|
||||
|
@ -20,13 +20,13 @@ from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.enum import try_parse_enum
|
||||
|
||||
from .entity import EsphomeEntity, platform_async_setup_entry
|
||||
from .entry_data import ESPHomeConfigEntry
|
||||
from .enum_mapper import EsphomeEnumMapper
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
@ -34,7 +34,7 @@ PARALLEL_UPDATES = 0
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: ESPHomeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up esphome sensors based on a config entry."""
|
||||
|
@ -184,6 +184,15 @@
|
||||
"exceptions": {
|
||||
"action_call_failed": {
|
||||
"message": "Failed to execute the action call {call_name} on {device_name}: {error}"
|
||||
},
|
||||
"error_communicating_with_device": {
|
||||
"message": "Error communicating with the device {device_name}: {error}"
|
||||
},
|
||||
"error_compiling": {
|
||||
"message": "Error compiling {configuration}; Try again in ESPHome dashboard for more information."
|
||||
},
|
||||
"error_uploading": {
|
||||
"message": "Error during OTA of {configuration}; Try again in ESPHome dashboard for more information."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ from homeassistant.components.update import (
|
||||
UpdateEntity,
|
||||
UpdateEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
@ -27,6 +26,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util.enum import try_parse_enum
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import ESPHomeDashboardCoordinator
|
||||
from .dashboard import async_get_dashboard
|
||||
from .domain_data import DomainData
|
||||
@ -36,7 +36,7 @@ from .entity import (
|
||||
esphome_state_property,
|
||||
platform_async_setup_entry,
|
||||
)
|
||||
from .entry_data import RuntimeEntryData
|
||||
from .entry_data import ESPHomeConfigEntry, RuntimeEntryData
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@ -47,7 +47,7 @@ NO_FEATURES = UpdateEntityFeature(0)
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: ESPHomeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up ESPHome update based on a config entry."""
|
||||
@ -202,16 +202,23 @@ class ESPHomeDashboardUpdateEntity(
|
||||
api = coordinator.api
|
||||
device = coordinator.data.get(self._device_info.name)
|
||||
assert device is not None
|
||||
configuration = device["configuration"]
|
||||
try:
|
||||
if not await api.compile(device["configuration"]):
|
||||
if not await api.compile(configuration):
|
||||
raise HomeAssistantError(
|
||||
f"Error compiling {device['configuration']}; "
|
||||
"Try again in ESPHome dashboard for more information."
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="error_compiling",
|
||||
translation_placeholders={
|
||||
"configuration": configuration,
|
||||
},
|
||||
)
|
||||
if not await api.upload(device["configuration"], "OTA"):
|
||||
if not await api.upload(configuration, "OTA"):
|
||||
raise HomeAssistantError(
|
||||
f"Error updating {device['configuration']} via OTA; "
|
||||
"Try again in ESPHome dashboard for more information."
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="error_uploading",
|
||||
translation_placeholders={
|
||||
"configuration": configuration,
|
||||
},
|
||||
)
|
||||
finally:
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
@ -68,6 +68,9 @@
|
||||
"repeat_set": {
|
||||
"service": "mdi:repeat"
|
||||
},
|
||||
"search_media": {
|
||||
"service": "mdi:text-search"
|
||||
},
|
||||
"select_sound_mode": {
|
||||
"service": "mdi:surround-sound"
|
||||
},
|
||||
|
@ -181,6 +181,35 @@ browse_media:
|
||||
selector:
|
||||
text:
|
||||
|
||||
search_media:
|
||||
target:
|
||||
entity:
|
||||
domain: media_player
|
||||
supported_features:
|
||||
- media_player.MediaPlayerEntityFeature.SEARCH_MEDIA
|
||||
fields:
|
||||
search_query:
|
||||
required: true
|
||||
example: "Beatles"
|
||||
selector:
|
||||
text:
|
||||
media_content_type:
|
||||
required: false
|
||||
example: "music"
|
||||
selector:
|
||||
text:
|
||||
media_content_id:
|
||||
required: false
|
||||
example: "A:ALBUMARTIST/Beatles"
|
||||
selector:
|
||||
text:
|
||||
media_filter_classes:
|
||||
required: false
|
||||
example: ["album", "artist"]
|
||||
selector:
|
||||
text:
|
||||
multiple: true
|
||||
|
||||
select_source:
|
||||
target:
|
||||
entity:
|
||||
|
@ -274,6 +274,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"search_media": {
|
||||
"name": "Search media",
|
||||
"description": "Searches the available media.",
|
||||
"fields": {
|
||||
"media_content_id": {
|
||||
"name": "[%key:component::media_player::services::browse_media::fields::media_content_id::name%]",
|
||||
"description": "[%key:component::media_player::services::browse_media::fields::media_content_id::description%]"
|
||||
},
|
||||
"media_content_type": {
|
||||
"name": "[%key:component::media_player::services::browse_media::fields::media_content_type::name%]",
|
||||
"description": "[%key:component::media_player::services::browse_media::fields::media_content_type::description%]"
|
||||
},
|
||||
"search_query": {
|
||||
"name": "Search query",
|
||||
"description": "The term to search for."
|
||||
},
|
||||
"media_filter_classes": {
|
||||
"name": "Media filter classes",
|
||||
"description": "List of media classes to filter the search results by."
|
||||
}
|
||||
}
|
||||
},
|
||||
"select_source": {
|
||||
"name": "Select source",
|
||||
"description": "Sends the media player the command to change input source.",
|
||||
|
@ -33,7 +33,7 @@ from .const import (
|
||||
URI_SCHEME,
|
||||
URI_SCHEME_REGEX,
|
||||
)
|
||||
from .error import MediaSourceError, Unresolvable
|
||||
from .error import MediaSourceError, UnknownMediaSource, Unresolvable
|
||||
from .models import BrowseMediaSource, MediaSource, MediaSourceItem, PlayMedia
|
||||
|
||||
__all__ = [
|
||||
@ -113,7 +113,11 @@ def _get_media_item(
|
||||
return MediaSourceItem(hass, domain, "", target_media_player)
|
||||
|
||||
if item.domain is not None and item.domain not in hass.data[DOMAIN]:
|
||||
raise ValueError("Unknown media source")
|
||||
raise UnknownMediaSource(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="unknown_media_source",
|
||||
translation_placeholders={"domain": item.domain},
|
||||
)
|
||||
|
||||
return item
|
||||
|
||||
@ -132,7 +136,14 @@ async def async_browse_media(
|
||||
try:
|
||||
item = await _get_media_item(hass, media_content_id, None).async_browse()
|
||||
except ValueError as err:
|
||||
raise BrowseError(str(err)) from err
|
||||
raise BrowseError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="browse_media_failed",
|
||||
translation_placeholders={
|
||||
"media_content_id": str(media_content_id),
|
||||
"error": str(err),
|
||||
},
|
||||
) from err
|
||||
|
||||
if content_filter is None or item.children is None:
|
||||
return item
|
||||
@ -165,7 +176,14 @@ async def async_resolve_media(
|
||||
try:
|
||||
item = _get_media_item(hass, media_content_id, target_media_player)
|
||||
except ValueError as err:
|
||||
raise Unresolvable(str(err)) from err
|
||||
raise Unresolvable(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="resolve_media_failed",
|
||||
translation_placeholders={
|
||||
"media_content_id": str(media_content_id),
|
||||
"error": str(err),
|
||||
},
|
||||
) from err
|
||||
|
||||
return await item.async_resolve()
|
||||
|
||||
|
@ -9,3 +9,7 @@ class MediaSourceError(HomeAssistantError):
|
||||
|
||||
class Unresolvable(MediaSourceError):
|
||||
"""When media ID is not resolvable."""
|
||||
|
||||
|
||||
class UnknownMediaSource(MediaSourceError, ValueError):
|
||||
"""When media source is unknown."""
|
||||
|
13
homeassistant/components/media_source/strings.json
Normal file
13
homeassistant/components/media_source/strings.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"exceptions": {
|
||||
"browse_media_failed": {
|
||||
"message": "Failed to browse media with content id {media_content_id}: {error}"
|
||||
},
|
||||
"resolve_media_failed": {
|
||||
"message": "Failed to resolve media with content id {media_content_id}: {error}"
|
||||
},
|
||||
"unknown_media_source": {
|
||||
"message": "Unknown media source: {domain}"
|
||||
}
|
||||
}
|
||||
}
|
@ -81,6 +81,7 @@ async def test_restore_dashboard_storage_end_to_end(
|
||||
assert mock_dashboard_api.mock_calls[0][1][0] == "http://new-host:6052"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("hassio_stubs")
|
||||
async def test_restore_dashboard_storage_skipped_if_addon_uninstalled(
|
||||
hass: HomeAssistant,
|
||||
hass_storage: dict[str, Any],
|
||||
@ -105,9 +106,7 @@ async def test_restore_dashboard_storage_skipped_if_addon_uninstalled(
|
||||
return_value={},
|
||||
),
|
||||
):
|
||||
await async_setup_component(hass, "hassio", {})
|
||||
await hass.async_block_till_done()
|
||||
await async_setup_component(hass, DOMAIN, {})
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
assert "test-slug is no longer installed" in caplog.text
|
||||
assert not mock_dashboard_api.called
|
||||
|
@ -3,17 +3,16 @@
|
||||
from collections.abc import Generator
|
||||
import os
|
||||
import re
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from aiohasupervisor.models import AddonsStats, AddonState
|
||||
from aiohttp.test_utils import TestClient
|
||||
import pytest
|
||||
|
||||
from homeassistant.auth.models import RefreshToken
|
||||
from homeassistant.components.hassio.handler import HassIO, HassioAPIError
|
||||
from homeassistant.components.hassio.handler import HassIO
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import SUPERVISOR_TOKEN
|
||||
|
||||
@ -31,55 +30,6 @@ def disable_security_filter() -> Generator[None]:
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hassio_env(supervisor_is_connected: AsyncMock) -> Generator[None]:
|
||||
"""Fixture to inject hassio env."""
|
||||
with (
|
||||
patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}),
|
||||
patch.dict(os.environ, {"SUPERVISOR_TOKEN": SUPERVISOR_TOKEN}),
|
||||
patch(
|
||||
"homeassistant.components.hassio.HassIO.get_info",
|
||||
Mock(side_effect=HassioAPIError()),
|
||||
),
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def hassio_stubs(
|
||||
hassio_env: None,
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
) -> RefreshToken:
|
||||
"""Create mock hassio http client."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.hassio.HassIO.update_hass_api",
|
||||
return_value={"result": "ok"},
|
||||
) as hass_api,
|
||||
patch(
|
||||
"homeassistant.components.hassio.HassIO.update_hass_timezone",
|
||||
return_value={"result": "ok"},
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.hassio.HassIO.get_info",
|
||||
side_effect=HassioAPIError(),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.hassio.HassIO.get_ingress_panels",
|
||||
return_value={"panels": []},
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.hassio.issues.SupervisorIssues.setup",
|
||||
),
|
||||
):
|
||||
await async_setup_component(hass, "hassio", {})
|
||||
|
||||
return hass_api.call_args[0][1]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def hassio_client(
|
||||
hassio_stubs: RefreshToken, hass: HomeAssistant, hass_client: ClientSessionGenerator
|
||||
|
@ -57,8 +57,10 @@ async def test_async_browse_media(hass: HomeAssistant) -> None:
|
||||
# Test invalid base
|
||||
with pytest.raises(BrowseError) as excinfo:
|
||||
await async_browse_media(hass, f"{URI_SCHEME}{DOMAIN}/")
|
||||
assert str(excinfo.value) == "Invalid media source URI"
|
||||
|
||||
assert str(excinfo.value) == (
|
||||
"Failed to browse media with content id media-source://netatmo/: "
|
||||
"Invalid media source URI"
|
||||
)
|
||||
# Test successful listing
|
||||
media = await async_browse_media(hass, f"{URI_SCHEME}{DOMAIN}/events")
|
||||
|
||||
|
@ -119,8 +119,10 @@ from .typing import (
|
||||
if TYPE_CHECKING:
|
||||
# Local import to avoid processing recorder and SQLite modules when running a
|
||||
# testcase which does not use the recorder.
|
||||
from homeassistant.auth.models import RefreshToken
|
||||
from homeassistant.components import recorder
|
||||
|
||||
|
||||
pytest.register_assert_rewrite("tests.common")
|
||||
|
||||
from .common import ( # noqa: E402, isort:skip
|
||||
@ -1894,6 +1896,67 @@ def mock_bleak_scanner_start() -> Generator[MagicMock]:
|
||||
yield mock_bleak_scanner_start
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hassio_env(supervisor_is_connected: AsyncMock) -> Generator[None]:
|
||||
"""Fixture to inject hassio env."""
|
||||
from homeassistant.components.hassio import ( # pylint: disable=import-outside-toplevel
|
||||
HassioAPIError,
|
||||
)
|
||||
|
||||
from .components.hassio import ( # pylint: disable=import-outside-toplevel
|
||||
SUPERVISOR_TOKEN,
|
||||
)
|
||||
|
||||
with (
|
||||
patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}),
|
||||
patch.dict(os.environ, {"SUPERVISOR_TOKEN": SUPERVISOR_TOKEN}),
|
||||
patch(
|
||||
"homeassistant.components.hassio.HassIO.get_info",
|
||||
Mock(side_effect=HassioAPIError()),
|
||||
),
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def hassio_stubs(
|
||||
hassio_env: None,
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
) -> RefreshToken:
|
||||
"""Create mock hassio http client."""
|
||||
from homeassistant.components.hassio import ( # pylint: disable=import-outside-toplevel
|
||||
HassioAPIError,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.hassio.HassIO.update_hass_api",
|
||||
return_value={"result": "ok"},
|
||||
) as hass_api,
|
||||
patch(
|
||||
"homeassistant.components.hassio.HassIO.update_hass_timezone",
|
||||
return_value={"result": "ok"},
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.hassio.HassIO.get_info",
|
||||
side_effect=HassioAPIError(),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.hassio.HassIO.get_ingress_panels",
|
||||
return_value={"panels": []},
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.hassio.issues.SupervisorIssues.setup",
|
||||
),
|
||||
):
|
||||
await async_setup_component(hass, "hassio", {})
|
||||
|
||||
return hass_api.call_args[0][1]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def integration_frame_path() -> str:
|
||||
"""Return the path to the integration frame.
|
||||
|
Loading…
x
Reference in New Issue
Block a user