This commit is contained in:
Paulus Schoutsen 2022-12-12 22:36:56 -05:00 committed by GitHub
commit 05c429bcd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 211 additions and 52 deletions

View File

@ -7,9 +7,9 @@
"quality_scale": "internal", "quality_scale": "internal",
"requirements": [ "requirements": [
"bleak==0.19.2", "bleak==0.19.2",
"bleak-retry-connector==2.10.1", "bleak-retry-connector==2.10.2",
"bluetooth-adapters==0.12.0", "bluetooth-adapters==0.12.0",
"bluetooth-auto-recovery==0.5.5", "bluetooth-auto-recovery==1.0.0",
"bluetooth-data-tools==0.3.0", "bluetooth-data-tools==0.3.0",
"dbus-fast==1.75.0" "dbus-fast==1.75.0"
], ],

View File

@ -130,6 +130,7 @@ class HaScanner(BaseHaScanner):
new_info_callback: Callable[[BluetoothServiceInfoBleak], None], new_info_callback: Callable[[BluetoothServiceInfoBleak], None],
) -> None: ) -> None:
"""Init bluetooth discovery.""" """Init bluetooth discovery."""
self.mac_address = address
source = address if address != DEFAULT_ADDRESS else adapter or SOURCE_LOCAL source = address if address != DEFAULT_ADDRESS else adapter or SOURCE_LOCAL
super().__init__(hass, source, adapter) super().__init__(hass, source, adapter)
self.mode = mode self.mode = mode
@ -375,7 +376,7 @@ class HaScanner(BaseHaScanner):
# so we log at debug level. If we later come up with a repair # so we log at debug level. If we later come up with a repair
# strategy, we will change this to raise a repair issue as well. # strategy, we will change this to raise a repair issue as well.
_LOGGER.debug("%s: adapter stopped responding; executing reset", self.name) _LOGGER.debug("%s: adapter stopped responding; executing reset", self.name)
result = await async_reset_adapter(self.adapter) result = await async_reset_adapter(self.adapter, self.mac_address)
_LOGGER.debug("%s: adapter reset result: %s", self.name, result) _LOGGER.debug("%s: adapter reset result: %s", self.name, result)
async def async_stop(self) -> None: async def async_stop(self) -> None:

View File

@ -36,9 +36,9 @@ def async_load_history_from_system(
} }
async def async_reset_adapter(adapter: str | None) -> bool | None: async def async_reset_adapter(adapter: str | None, mac_address: str) -> bool | None:
"""Reset the adapter.""" """Reset the adapter."""
if adapter and adapter.startswith("hci"): if adapter and adapter.startswith("hci"):
adapter_id = int(adapter[3:]) adapter_id = int(adapter[3:])
return await recover_adapter(adapter_id) return await recover_adapter(adapter_id, mac_address)
return False return False

View File

@ -3,7 +3,7 @@
"name": "Google Cast", "name": "Google Cast",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/cast", "documentation": "https://www.home-assistant.io/integrations/cast",
"requirements": ["pychromecast==13.0.2"], "requirements": ["pychromecast==13.0.3"],
"after_dependencies": [ "after_dependencies": [
"cloud", "cloud",
"http", "http",

View File

@ -13,7 +13,7 @@ from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import issue_registry as ir from homeassistant.helpers import issue_registry as ir
from .const import DOMAIN from .const import DOMAIN
from .subscription import async_subscription_info from .subscription import async_migrate_paypal_agreement, async_subscription_info
BACKOFF_TIME = 5 BACKOFF_TIME = 5
MAX_RETRIES = 60 # This allows for 10 minutes of retries MAX_RETRIES = 60 # This allows for 10 minutes of retries
@ -68,13 +68,13 @@ class LegacySubscriptionRepairFlow(RepairsFlow):
async def async_step_change_plan(self, _: None = None) -> FlowResult: async def async_step_change_plan(self, _: None = None) -> FlowResult:
"""Wait for the user to authorize the app installation.""" """Wait for the user to authorize the app installation."""
cloud: Cloud = self.hass.data[DOMAIN]
async def _async_wait_for_plan_change() -> None: async def _async_wait_for_plan_change() -> None:
flow_manager = repairs_flow_manager(self.hass) flow_manager = repairs_flow_manager(self.hass)
# We can not get here without a flow manager # We can not get here without a flow manager
assert flow_manager is not None assert flow_manager is not None
cloud: Cloud = self.hass.data[DOMAIN]
retries = 0 retries = 0
while retries < MAX_RETRIES: while retries < MAX_RETRIES:
self._data = await async_subscription_info(cloud) self._data = await async_subscription_info(cloud)
@ -90,9 +90,10 @@ class LegacySubscriptionRepairFlow(RepairsFlow):
if not self.wait_task: if not self.wait_task:
self.wait_task = self.hass.async_create_task(_async_wait_for_plan_change()) self.wait_task = self.hass.async_create_task(_async_wait_for_plan_change())
migration = await async_migrate_paypal_agreement(cloud)
return self.async_external_step( return self.async_external_step(
step_id="change_plan", step_id="change_plan",
url="https://account.nabucasa.com/", url=migration["url"] if migration else "https://account.nabucasa.com/",
) )
await self.wait_task await self.wait_task

View File

@ -1,6 +1,7 @@
"""Subscription information.""" """Subscription information."""
from __future__ import annotations from __future__ import annotations
import asyncio
import logging import logging
from typing import Any from typing import Any
@ -18,7 +19,28 @@ async def async_subscription_info(cloud: Cloud) -> dict[str, Any] | None:
try: try:
async with async_timeout.timeout(REQUEST_TIMEOUT): async with async_timeout.timeout(REQUEST_TIMEOUT):
return await cloud_api.async_subscription_info(cloud) return await cloud_api.async_subscription_info(cloud)
except asyncio.TimeoutError:
_LOGGER.error(
"A timeout of %s was reached while trying to fetch subscription information",
REQUEST_TIMEOUT,
)
except ClientError: except ClientError:
_LOGGER.error("Failed to fetch subscription information") _LOGGER.error("Failed to fetch subscription information")
return None return None
async def async_migrate_paypal_agreement(cloud: Cloud) -> dict[str, Any] | None:
"""Migrate a paypal agreement from legacy."""
try:
async with async_timeout.timeout(REQUEST_TIMEOUT):
return await cloud_api.async_migrate_paypal_agreement(cloud)
except asyncio.TimeoutError:
_LOGGER.error(
"A timeout of %s was reached while trying to start agreement migration",
REQUEST_TIMEOUT,
)
except ClientError as exception:
_LOGGER.error("Failed to start agreement migration - %s", exception)
return None

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from xml.etree.ElementTree import ParseError
from pyfritzhome import Fritzhome, FritzhomeDevice, LoginError from pyfritzhome import Fritzhome, FritzhomeDevice, LoginError
from pyfritzhome.devicetypes.fritzhomeentitybase import FritzhomeEntityBase from pyfritzhome.devicetypes.fritzhomeentitybase import FritzhomeEntityBase
@ -43,7 +44,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
CONF_CONNECTIONS: fritz, CONF_CONNECTIONS: fritz,
} }
coordinator = FritzboxDataUpdateCoordinator(hass, entry) try:
await hass.async_add_executor_job(fritz.update_templates)
except ParseError:
LOGGER.debug("Disable smarthome templates")
has_templates = False
else:
LOGGER.debug("Enable smarthome templates")
has_templates = True
coordinator = FritzboxDataUpdateCoordinator(hass, entry, has_templates)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()

View File

@ -3,7 +3,6 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from xml.etree.ElementTree import ParseError
from pyfritzhome import Fritzhome, FritzhomeDevice, LoginError from pyfritzhome import Fritzhome, FritzhomeDevice, LoginError
from pyfritzhome.devicetypes import FritzhomeTemplate from pyfritzhome.devicetypes import FritzhomeTemplate
@ -30,17 +29,14 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat
configuration_url: str configuration_url: str
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: def __init__(
self, hass: HomeAssistant, entry: ConfigEntry, has_templates: bool
) -> None:
"""Initialize the Fritzbox Smarthome device coordinator.""" """Initialize the Fritzbox Smarthome device coordinator."""
self.entry = entry self.entry = entry
self.fritz: Fritzhome = hass.data[DOMAIN][self.entry.entry_id][CONF_CONNECTIONS] self.fritz: Fritzhome = hass.data[DOMAIN][self.entry.entry_id][CONF_CONNECTIONS]
self.configuration_url = self.fritz.get_prefixed_host() self.configuration_url = self.fritz.get_prefixed_host()
self.has_templates = True self.has_templates = has_templates
try:
hass.async_add_executor_job(self.fritz.update_templates)
except ParseError:
LOGGER.info("Disable smarthome templates")
self.has_templates = False
super().__init__( super().__init__(
hass, hass,

View File

@ -2,7 +2,7 @@
"domain": "frontend", "domain": "frontend",
"name": "Home Assistant Frontend", "name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend", "documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": ["home-assistant-frontend==20221208.0"], "requirements": ["home-assistant-frontend==20221212.0"],
"dependencies": [ "dependencies": [
"api", "api",
"auth", "auth",

View File

@ -18,7 +18,7 @@ from homeassistant.const import (
SERVICE_TURN_ON, SERVICE_TURN_ON,
STATE_ON, STATE_ON,
) )
from homeassistant.core import callback from homeassistant.core import State, callback
from .accessories import TYPES, HomeAccessory from .accessories import TYPES, HomeAccessory
from .const import ( from .const import (
@ -96,7 +96,7 @@ class RemoteInputSelectAccessory(HomeAccessory):
self.sources = [] self.sources = []
self.support_select_source = False self.support_select_source = False
if features & required_feature: if features & required_feature:
sources = state.attributes.get(source_list_key, []) sources = self._get_ordered_source_list_from_state(state)
if len(sources) > MAXIMUM_SOURCES: if len(sources) > MAXIMUM_SOURCES:
_LOGGER.warning( _LOGGER.warning(
"%s: Reached maximum number of sources (%s)", "%s: Reached maximum number of sources (%s)",
@ -143,6 +143,21 @@ class RemoteInputSelectAccessory(HomeAccessory):
serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE, value=False) serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE, value=False)
_LOGGER.debug("%s: Added source %s", self.entity_id, source) _LOGGER.debug("%s: Added source %s", self.entity_id, source)
def _get_ordered_source_list_from_state(self, state: State) -> list[str]:
"""Return ordered source list while preserving order with duplicates removed.
Some integrations have duplicate sources in the source list
which will make the source list conflict as HomeKit requires
unique source names.
"""
seen = set()
sources: list[str] = []
for source in state.attributes.get(self.source_list_key, []):
if source not in seen:
sources.append(source)
seen.add(source)
return sources
@abstractmethod @abstractmethod
def set_on_off(self, value): def set_on_off(self, value):
"""Move switch state to value if call came from HomeKit.""" """Move switch state to value if call came from HomeKit."""
@ -169,7 +184,7 @@ class RemoteInputSelectAccessory(HomeAccessory):
self.char_input_source.set_value(index) self.char_input_source.set_value(index)
return return
possible_sources = new_state.attributes.get(self.source_list_key, []) possible_sources = self._get_ordered_source_list_from_state(new_state)
if source in possible_sources: if source in possible_sources:
index = possible_sources.index(source) index = possible_sources.index(source)
if index >= MAXIMUM_SOURCES: if index >= MAXIMUM_SOURCES:

View File

@ -4,7 +4,7 @@
"config_flow": true, "config_flow": true,
"integration_type": "hub", "integration_type": "hub",
"documentation": "https://www.home-assistant.io/integrations/overkiz", "documentation": "https://www.home-assistant.io/integrations/overkiz",
"requirements": ["pyoverkiz==1.7.1"], "requirements": ["pyoverkiz==1.7.2"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_kizbox._tcp.local.", "type": "_kizbox._tcp.local.",

View File

@ -305,14 +305,6 @@ class DomesticHotWaterProduction(OverkizEntity, WaterHeaterEntity):
OverkizCommand.SET_BOOST_MODE, OverkizCommand.OFF OverkizCommand.SET_BOOST_MODE, OverkizCommand.OFF
) )
if self.executor.has_command(OverkizCommand.SET_BOOST_MODE_DURATION):
await self.executor.async_execute_command(
OverkizCommand.SET_BOOST_MODE_DURATION, 0
)
await self.executor.async_execute_command(
OverkizCommand.REFRESH_BOOST_MODE_DURATION
)
if self.executor.has_command(OverkizCommand.SET_CURRENT_OPERATING_MODE): if self.executor.has_command(OverkizCommand.SET_CURRENT_OPERATING_MODE):
current_operating_mode = self.executor.select_state( current_operating_mode = self.executor.select_state(
OverkizState.CORE_OPERATING_MODE OverkizState.CORE_OPERATING_MODE
@ -331,5 +323,10 @@ class DomesticHotWaterProduction(OverkizEntity, WaterHeaterEntity):
OverkizCommand.SET_DHW_MODE, self.overkiz_to_operation_mode[operation_mode] OverkizCommand.SET_DHW_MODE, self.overkiz_to_operation_mode[operation_mode]
) )
if self.executor.has_command(OverkizCommand.REFRESH_BOOST_MODE_DURATION):
await self.executor.async_execute_command(
OverkizCommand.REFRESH_BOOST_MODE_DURATION
)
if self.executor.has_command(OverkizCommand.REFRESH_DHW_MODE): if self.executor.has_command(OverkizCommand.REFRESH_DHW_MODE):
await self.executor.async_execute_command(OverkizCommand.REFRESH_DHW_MODE) await self.executor.async_execute_command(OverkizCommand.REFRESH_DHW_MODE)

View File

@ -85,7 +85,7 @@ class SleepIQFlowHandler(ConfigFlow, domain=DOMAIN):
self._reauth_entry = self.hass.config_entries.async_get_entry( self._reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"] self.context["entry_id"]
) )
return await self.async_step_reauth_confirm(dict(entry_data)) return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm( async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None

View File

@ -7,7 +7,7 @@
"bellows==0.34.5", "bellows==0.34.5",
"pyserial==3.5", "pyserial==3.5",
"pyserial-asyncio==0.6", "pyserial-asyncio==0.6",
"zha-quirks==0.0.88", "zha-quirks==0.0.89",
"zigpy-deconz==0.19.2", "zigpy-deconz==0.19.2",
"zigpy==0.52.3", "zigpy==0.52.3",
"zigpy-xbee==0.16.2", "zigpy-xbee==0.16.2",

View File

@ -8,7 +8,7 @@ from .backports.enum import StrEnum
APPLICATION_NAME: Final = "HomeAssistant" APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2022 MAJOR_VERSION: Final = 2022
MINOR_VERSION: Final = 12 MINOR_VERSION: Final = 12
PATCH_VERSION: Final = "3" PATCH_VERSION: Final = "4"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)

View File

@ -10,10 +10,10 @@ atomicwrites-homeassistant==1.4.1
attrs==21.2.0 attrs==21.2.0
awesomeversion==22.9.0 awesomeversion==22.9.0
bcrypt==3.1.7 bcrypt==3.1.7
bleak-retry-connector==2.10.1 bleak-retry-connector==2.10.2
bleak==0.19.2 bleak==0.19.2
bluetooth-adapters==0.12.0 bluetooth-adapters==0.12.0
bluetooth-auto-recovery==0.5.5 bluetooth-auto-recovery==1.0.0
bluetooth-data-tools==0.3.0 bluetooth-data-tools==0.3.0
certifi>=2021.5.30 certifi>=2021.5.30
ciso8601==2.2.0 ciso8601==2.2.0
@ -22,7 +22,7 @@ dbus-fast==1.75.0
fnvhash==0.1.0 fnvhash==0.1.0
hass-nabucasa==0.61.0 hass-nabucasa==0.61.0
home-assistant-bluetooth==1.8.1 home-assistant-bluetooth==1.8.1
home-assistant-frontend==20221208.0 home-assistant-frontend==20221212.0
httpx==0.23.1 httpx==0.23.1
ifaddr==0.1.7 ifaddr==0.1.7
janus==1.0.0 janus==1.0.0

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "homeassistant" name = "homeassistant"
version = "2022.12.3" version = "2022.12.4"
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3." description = "Open-source home automation platform running on Python 3."
readme = "README.rst" readme = "README.rst"

View File

@ -422,7 +422,7 @@ bimmer_connected==0.10.4
bizkaibus==0.1.1 bizkaibus==0.1.1
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bleak-retry-connector==2.10.1 bleak-retry-connector==2.10.2
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bleak==0.19.2 bleak==0.19.2
@ -450,7 +450,7 @@ bluemaestro-ble==0.2.0
bluetooth-adapters==0.12.0 bluetooth-adapters==0.12.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bluetooth-auto-recovery==0.5.5 bluetooth-auto-recovery==1.0.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
# homeassistant.components.led_ble # homeassistant.components.led_ble
@ -884,7 +884,7 @@ hole==0.7.0
holidays==0.17.2 holidays==0.17.2
# homeassistant.components.frontend # homeassistant.components.frontend
home-assistant-frontend==20221208.0 home-assistant-frontend==20221212.0
# homeassistant.components.home_connect # homeassistant.components.home_connect
homeconnect==0.7.2 homeconnect==0.7.2
@ -1504,7 +1504,7 @@ pycfdns==2.0.1
pychannels==1.2.3 pychannels==1.2.3
# homeassistant.components.cast # homeassistant.components.cast
pychromecast==13.0.2 pychromecast==13.0.3
# homeassistant.components.pocketcasts # homeassistant.components.pocketcasts
pycketcasts==1.0.1 pycketcasts==1.0.1
@ -1812,7 +1812,7 @@ pyotgw==2.1.3
pyotp==2.7.0 pyotp==2.7.0
# homeassistant.components.overkiz # homeassistant.components.overkiz
pyoverkiz==1.7.1 pyoverkiz==1.7.2
# homeassistant.components.openweathermap # homeassistant.components.openweathermap
pyowm==3.2.0 pyowm==3.2.0
@ -2639,7 +2639,7 @@ zengge==0.2
zeroconf==0.39.4 zeroconf==0.39.4
# homeassistant.components.zha # homeassistant.components.zha
zha-quirks==0.0.88 zha-quirks==0.0.89
# homeassistant.components.zhong_hong # homeassistant.components.zhong_hong
zhong_hong_hvac==1.0.9 zhong_hong_hvac==1.0.9

View File

@ -346,7 +346,7 @@ bellows==0.34.5
bimmer_connected==0.10.4 bimmer_connected==0.10.4
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bleak-retry-connector==2.10.1 bleak-retry-connector==2.10.2
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bleak==0.19.2 bleak==0.19.2
@ -364,7 +364,7 @@ bluemaestro-ble==0.2.0
bluetooth-adapters==0.12.0 bluetooth-adapters==0.12.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bluetooth-auto-recovery==0.5.5 bluetooth-auto-recovery==1.0.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
# homeassistant.components.led_ble # homeassistant.components.led_ble
@ -664,7 +664,7 @@ hole==0.7.0
holidays==0.17.2 holidays==0.17.2
# homeassistant.components.frontend # homeassistant.components.frontend
home-assistant-frontend==20221208.0 home-assistant-frontend==20221212.0
# homeassistant.components.home_connect # homeassistant.components.home_connect
homeconnect==0.7.2 homeconnect==0.7.2
@ -1077,7 +1077,7 @@ pybravia==0.2.3
pycfdns==2.0.1 pycfdns==2.0.1
# homeassistant.components.cast # homeassistant.components.cast
pychromecast==13.0.2 pychromecast==13.0.3
# homeassistant.components.comfoconnect # homeassistant.components.comfoconnect
pycomfoconnect==0.4 pycomfoconnect==0.4
@ -1289,7 +1289,7 @@ pyotgw==2.1.3
pyotp==2.7.0 pyotp==2.7.0
# homeassistant.components.overkiz # homeassistant.components.overkiz
pyoverkiz==1.7.1 pyoverkiz==1.7.2
# homeassistant.components.openweathermap # homeassistant.components.openweathermap
pyowm==3.2.0 pyowm==3.2.0
@ -1840,7 +1840,7 @@ zamg==0.1.1
zeroconf==0.39.4 zeroconf==0.39.4
# homeassistant.components.zha # homeassistant.components.zha
zha-quirks==0.0.88 zha-quirks==0.0.89
# homeassistant.components.zha # homeassistant.components.zha
zigpy-deconz==0.19.2 zigpy-deconz==0.19.2

View File

@ -88,6 +88,10 @@ async def test_legacy_subscription_repair_flow(
"https://accounts.nabucasa.com/payments/subscription_info", "https://accounts.nabucasa.com/payments/subscription_info",
json={"provider": None}, json={"provider": None},
) )
aioclient_mock.post(
"https://accounts.nabucasa.com/payments/migrate_paypal_agreement",
json={"url": "https://paypal.com"},
)
cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": "legacy"}) cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": "legacy"})
repair_issue = issue_registry.async_get_issue( repair_issue = issue_registry.async_get_issue(
@ -133,7 +137,7 @@ async def test_legacy_subscription_repair_flow(
"flow_id": flow_id, "flow_id": flow_id,
"handler": DOMAIN, "handler": DOMAIN,
"step_id": "change_plan", "step_id": "change_plan",
"url": "https://account.nabucasa.com/", "url": "https://paypal.com",
"description_placeholders": None, "description_placeholders": None,
} }
@ -161,8 +165,15 @@ async def test_legacy_subscription_repair_flow(
async def test_legacy_subscription_repair_flow_timeout( async def test_legacy_subscription_repair_flow_timeout(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: Callable[..., Awaitable[ClientSession]], hass_client: Callable[..., Awaitable[ClientSession]],
mock_auth: Generator[None, AsyncMock, None],
aioclient_mock: AiohttpClientMocker,
): ):
"""Test timeout flow of the fix flow for legacy subscription.""" """Test timeout flow of the fix flow for legacy subscription."""
aioclient_mock.post(
"https://accounts.nabucasa.com/payments/migrate_paypal_agreement",
status=403,
)
issue_registry: ir.IssueRegistry = ir.async_get(hass) issue_registry: ir.IssueRegistry = ir.async_get(hass)
cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": "legacy"}) cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": "legacy"})

View File

@ -0,0 +1,61 @@
"""Test cloud subscription functions."""
import asyncio
from unittest.mock import AsyncMock, Mock
from hass_nabucasa import Cloud
import pytest
from homeassistant.components.cloud.subscription import (
async_migrate_paypal_agreement,
async_subscription_info,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.fixture(name="mocked_cloud")
def mocked_cloud_object(hass: HomeAssistant) -> Cloud:
"""Mock cloud object."""
return Mock(
accounts_server="accounts.nabucasa.com",
auth=Mock(async_check_token=AsyncMock()),
websession=async_get_clientsession(hass),
)
async def test_fetching_subscription_with_timeout_error(
aioclient_mock: AiohttpClientMocker,
caplog: pytest.LogCaptureFixture,
mocked_cloud: Cloud,
):
"""Test that we handle timeout error."""
aioclient_mock.get(
"https://accounts.nabucasa.com/payments/subscription_info",
exc=asyncio.TimeoutError(),
)
assert await async_subscription_info(mocked_cloud) is None
assert (
"A timeout of 10 was reached while trying to fetch subscription information"
in caplog.text
)
async def test_migrate_paypal_agreement_with_timeout_error(
aioclient_mock: AiohttpClientMocker,
caplog: pytest.LogCaptureFixture,
mocked_cloud: Cloud,
):
"""Test that we handle timeout error."""
aioclient_mock.post(
"https://accounts.nabucasa.com/payments/migrate_paypal_agreement",
exc=asyncio.TimeoutError(),
)
assert await async_migrate_paypal_agreement(mocked_cloud) is None
assert (
"A timeout of 10 was reached while trying to start agreement migration"
in caplog.text
)

View File

@ -512,3 +512,48 @@ async def test_media_player_television_max_sources(hass, hk_driver, events, capl
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert acc.char_input_source.value == 0 assert acc.char_input_source.value == 0
async def test_media_player_television_duplicate_sources(
hass, hk_driver, events, caplog
):
"""Test if television accessory with duplicate sources."""
entity_id = "media_player.television"
sources = ["MUSIC", "HDMI", "SCREEN MIRRORING", "HDMI", "MUSIC"]
hass.states.async_set(
entity_id,
None,
{
ATTR_DEVICE_CLASS: MediaPlayerDeviceClass.TV,
ATTR_SUPPORTED_FEATURES: 3469,
ATTR_MEDIA_VOLUME_MUTED: False,
ATTR_INPUT_SOURCE: "HDMI",
ATTR_INPUT_SOURCE_LIST: sources,
},
)
await hass.async_block_till_done()
acc = TelevisionMediaPlayer(hass, hk_driver, "MediaPlayer", entity_id, 2, None)
await acc.run()
await hass.async_block_till_done()
assert acc.aid == 2
assert acc.category == 31 # Television
assert acc.char_active.value == 0
assert acc.char_remote_key.value == 0
assert acc.char_input_source.value == 1
assert acc.char_mute.value is False
hass.states.async_set(
entity_id,
None,
{
ATTR_DEVICE_CLASS: MediaPlayerDeviceClass.TV,
ATTR_SUPPORTED_FEATURES: 3469,
ATTR_MEDIA_VOLUME_MUTED: False,
ATTR_INPUT_SOURCE: "MUSIC",
ATTR_INPUT_SOURCE_LIST: sources,
},
)
await hass.async_block_till_done()
assert acc.char_input_source.value == 0