Add repair for legacy subscription to cloud integration (#82621)
This commit is contained in:
parent
82b2aaaa3e
commit
7c82b78f8c
9 changed files with 442 additions and 14 deletions
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
|
from datetime import timedelta
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from hass_nabucasa import Cloud
|
from hass_nabucasa import Cloud
|
||||||
|
@ -26,6 +27,7 @@ from homeassistant.helpers.dispatcher import (
|
||||||
async_dispatcher_connect,
|
async_dispatcher_connect,
|
||||||
async_dispatcher_send,
|
async_dispatcher_send,
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers.event import async_call_later
|
||||||
from homeassistant.helpers.service import async_register_admin_service
|
from homeassistant.helpers.service import async_register_admin_service
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
|
@ -55,6 +57,8 @@ from .const import (
|
||||||
MODE_PROD,
|
MODE_PROD,
|
||||||
)
|
)
|
||||||
from .prefs import CloudPreferences
|
from .prefs import CloudPreferences
|
||||||
|
from .repairs import async_manage_legacy_subscription_issue
|
||||||
|
from .subscription import async_subscription_info
|
||||||
|
|
||||||
DEFAULT_MODE = MODE_PROD
|
DEFAULT_MODE = MODE_PROD
|
||||||
|
|
||||||
|
@ -258,6 +262,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
|
|
||||||
loaded = False
|
loaded = False
|
||||||
|
|
||||||
|
async def async_startup_repairs(_=None) -> None:
|
||||||
|
"""Create repair issues after startup."""
|
||||||
|
if not cloud.is_logged_in:
|
||||||
|
return
|
||||||
|
|
||||||
|
if subscription_info := await async_subscription_info(cloud):
|
||||||
|
async_manage_legacy_subscription_issue(hass, subscription_info)
|
||||||
|
|
||||||
async def _on_connect():
|
async def _on_connect():
|
||||||
"""Discover RemoteUI binary sensor."""
|
"""Discover RemoteUI binary sensor."""
|
||||||
nonlocal loaded
|
nonlocal loaded
|
||||||
|
@ -294,6 +306,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
|
|
||||||
account_link.async_setup(hass)
|
account_link.async_setup(hass)
|
||||||
|
|
||||||
|
async_call_later(
|
||||||
|
hass=hass,
|
||||||
|
delay=timedelta(hours=1),
|
||||||
|
action=async_startup_repairs,
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from typing import Any
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import async_timeout
|
import async_timeout
|
||||||
import attr
|
import attr
|
||||||
from hass_nabucasa import Cloud, auth, cloud_api, thingtalk
|
from hass_nabucasa import Cloud, auth, thingtalk
|
||||||
from hass_nabucasa.const import STATE_DISCONNECTED
|
from hass_nabucasa.const import STATE_DISCONNECTED
|
||||||
from hass_nabucasa.voice import MAP_VOICE
|
from hass_nabucasa.voice import MAP_VOICE
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
@ -38,6 +38,8 @@ from .const import (
|
||||||
PREF_TTS_DEFAULT_VOICE,
|
PREF_TTS_DEFAULT_VOICE,
|
||||||
REQUEST_TIMEOUT,
|
REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
from .repairs import async_manage_legacy_subscription_issue
|
||||||
|
from .subscription import async_subscription_info
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -328,15 +330,14 @@ async def websocket_subscription(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle request for account info."""
|
"""Handle request for account info."""
|
||||||
cloud = hass.data[DOMAIN]
|
cloud = hass.data[DOMAIN]
|
||||||
try:
|
if (data := await async_subscription_info(cloud)) is None:
|
||||||
async with async_timeout.timeout(REQUEST_TIMEOUT):
|
|
||||||
data = await cloud_api.async_subscription_info(cloud)
|
|
||||||
except aiohttp.ClientError:
|
|
||||||
connection.send_error(
|
connection.send_error(
|
||||||
msg["id"], "request_failed", "Failed to request subscription"
|
msg["id"], "request_failed", "Failed to request subscription"
|
||||||
)
|
)
|
||||||
else:
|
return
|
||||||
|
|
||||||
connection.send_result(msg["id"], data)
|
connection.send_result(msg["id"], data)
|
||||||
|
async_manage_legacy_subscription_issue(hass, data)
|
||||||
|
|
||||||
|
|
||||||
@_require_cloud_login
|
@_require_cloud_login
|
||||||
|
|
121
homeassistant/components/cloud/repairs.py
Normal file
121
homeassistant/components/cloud/repairs.py
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
"""Repairs implementation for the cloud integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from hass_nabucasa import Cloud
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.repairs import RepairsFlow, repairs_flow_manager
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
from homeassistant.helpers import issue_registry as ir
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .subscription import async_subscription_info
|
||||||
|
|
||||||
|
BACKOFF_TIME = 5
|
||||||
|
MAX_RETRIES = 60 # This allows for 10 minutes of retries
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_manage_legacy_subscription_issue(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
subscription_info: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Manage the legacy subscription issue.
|
||||||
|
|
||||||
|
If the provider is "legacy" create an issue,
|
||||||
|
in all other cases remove the issue.
|
||||||
|
"""
|
||||||
|
if subscription_info["provider"] == "legacy":
|
||||||
|
ir.async_create_issue(
|
||||||
|
hass=hass,
|
||||||
|
domain=DOMAIN,
|
||||||
|
issue_id="legacy_subscription",
|
||||||
|
is_fixable=True,
|
||||||
|
severity=ir.IssueSeverity.WARNING,
|
||||||
|
translation_key="legacy_subscription",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
ir.async_delete_issue(hass=hass, domain=DOMAIN, issue_id="legacy_subscription")
|
||||||
|
|
||||||
|
|
||||||
|
class LegacySubscriptionRepairFlow(RepairsFlow):
|
||||||
|
"""Handler for an issue fixing flow."""
|
||||||
|
|
||||||
|
wait_task: asyncio.Task | None = None
|
||||||
|
_data: dict[str, Any] | None = None
|
||||||
|
|
||||||
|
async def async_step_init(self, _: None = None) -> FlowResult:
|
||||||
|
"""Handle the first step of a fix flow."""
|
||||||
|
return await self.async_step_confirm_change_plan()
|
||||||
|
|
||||||
|
async def async_step_confirm_change_plan(
|
||||||
|
self,
|
||||||
|
user_input: dict[str, str] | None = None,
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle the confirm step of a fix flow."""
|
||||||
|
if user_input is not None:
|
||||||
|
return await self.async_step_change_plan()
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="confirm_change_plan", data_schema=vol.Schema({})
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_change_plan(self, _: None = None) -> FlowResult:
|
||||||
|
"""Wait for the user to authorize the app installation."""
|
||||||
|
|
||||||
|
async def _async_wait_for_plan_change() -> None:
|
||||||
|
flow_manager = repairs_flow_manager(self.hass)
|
||||||
|
# We can not get here without a flow manager
|
||||||
|
assert flow_manager is not None
|
||||||
|
|
||||||
|
cloud: Cloud = self.hass.data[DOMAIN]
|
||||||
|
|
||||||
|
retries = 0
|
||||||
|
while retries < MAX_RETRIES:
|
||||||
|
self._data = await async_subscription_info(cloud)
|
||||||
|
if self._data is not None and self._data["provider"] != "legacy":
|
||||||
|
break
|
||||||
|
|
||||||
|
retries += 1
|
||||||
|
await asyncio.sleep(BACKOFF_TIME)
|
||||||
|
|
||||||
|
self.hass.async_create_task(
|
||||||
|
flow_manager.async_configure(flow_id=self.flow_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.wait_task:
|
||||||
|
self.wait_task = self.hass.async_create_task(_async_wait_for_plan_change())
|
||||||
|
return self.async_external_step(
|
||||||
|
step_id="change_plan",
|
||||||
|
url="https://account.nabucasa.com/",
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.wait_task
|
||||||
|
|
||||||
|
if self._data is None or self._data["provider"] == "legacy":
|
||||||
|
# If we get here we waited too long.
|
||||||
|
return self.async_external_step_done(next_step_id="timeout")
|
||||||
|
|
||||||
|
return self.async_external_step_done(next_step_id="complete")
|
||||||
|
|
||||||
|
async def async_step_complete(self, _: None = None) -> FlowResult:
|
||||||
|
"""Handle the final step of a fix flow."""
|
||||||
|
return self.async_create_entry(title="", data={})
|
||||||
|
|
||||||
|
async def async_step_timeout(self, _: None = None) -> FlowResult:
|
||||||
|
"""Handle the final step of a fix flow."""
|
||||||
|
return self.async_abort(reason="operation_took_too_long")
|
||||||
|
|
||||||
|
|
||||||
|
async def async_create_fix_flow(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
issue_id: str,
|
||||||
|
data: dict[str, str | int | float | None] | None,
|
||||||
|
) -> RepairsFlow:
|
||||||
|
"""Create flow."""
|
||||||
|
return LegacySubscriptionRepairFlow()
|
|
@ -13,5 +13,20 @@
|
||||||
"logged_in": "Logged In",
|
"logged_in": "Logged In",
|
||||||
"subscription_expiration": "Subscription Expiration"
|
"subscription_expiration": "Subscription Expiration"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"issues": {
|
||||||
|
"legacy_subscription": {
|
||||||
|
"title": "Legacy subscription detected",
|
||||||
|
"fix_flow": {
|
||||||
|
"step": {
|
||||||
|
"confirm_change_plan": {
|
||||||
|
"description": "We've recently updated our subscription system. To continue using Home Assistant Cloud you need to one-time approve the change in PayPal.\n\nThis takes 1 minute and will not increase the price."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"operation_took_too_long": "The operation took too long. Please try again later."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
24
homeassistant/components/cloud/subscription.py
Normal file
24
homeassistant/components/cloud/subscription.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
"""Subscription information."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from aiohttp.client_exceptions import ClientError
|
||||||
|
import async_timeout
|
||||||
|
from hass_nabucasa import Cloud, cloud_api
|
||||||
|
|
||||||
|
from .const import REQUEST_TIMEOUT
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_subscription_info(cloud: Cloud) -> dict[str, Any] | None:
|
||||||
|
"""Fetch the subscription info."""
|
||||||
|
try:
|
||||||
|
async with async_timeout.timeout(REQUEST_TIMEOUT):
|
||||||
|
return await cloud_api.async_subscription_info(cloud)
|
||||||
|
except ClientError:
|
||||||
|
_LOGGER.error("Failed to fetch subscription information")
|
||||||
|
|
||||||
|
return None
|
|
@ -1,4 +1,19 @@
|
||||||
{
|
{
|
||||||
|
"issues": {
|
||||||
|
"legacy_subscription": {
|
||||||
|
"fix_flow": {
|
||||||
|
"abort": {
|
||||||
|
"operation_took_too_long": "The operation took too long. Please try again later."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"confirm_change_plan": {
|
||||||
|
"description": "We've recently updated our subscription system. To continue using Home Assistant Cloud you need to one-time approve the change in PayPal.\n\nThis takes 1 minute and will not increase the price."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Legacy subscription detected"
|
||||||
|
}
|
||||||
|
},
|
||||||
"system_health": {
|
"system_health": {
|
||||||
"info": {
|
"info": {
|
||||||
"alexa_enabled": "Alexa Enabled",
|
"alexa_enabled": "Alexa Enabled",
|
||||||
|
|
|
@ -52,6 +52,15 @@ def mock_cloud_login(hass, mock_cloud_setup):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="mock_auth")
|
||||||
|
def mock_auth_fixture():
|
||||||
|
"""Mock check token."""
|
||||||
|
with patch("hass_nabucasa.auth.CognitoAuth.async_check_token"), patch(
|
||||||
|
"hass_nabucasa.auth.CognitoAuth.async_renew_access_token"
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_expired_cloud_login(hass, mock_cloud_setup):
|
def mock_expired_cloud_login(hass, mock_cloud_setup):
|
||||||
"""Mock cloud is logged in."""
|
"""Mock cloud is logged in."""
|
||||||
|
|
|
@ -24,13 +24,6 @@ from tests.components.google_assistant import MockConfig
|
||||||
SUBSCRIPTION_INFO_URL = "https://api-test.hass.io/subscription_info"
|
SUBSCRIPTION_INFO_URL = "https://api-test.hass.io/subscription_info"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="mock_auth")
|
|
||||||
def mock_auth_fixture():
|
|
||||||
"""Mock check token."""
|
|
||||||
with patch("hass_nabucasa.auth.CognitoAuth.async_check_token"):
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="mock_cloud_login")
|
@pytest.fixture(name="mock_cloud_login")
|
||||||
def mock_cloud_login_fixture(hass, setup_api):
|
def mock_cloud_login_fixture(hass, setup_api):
|
||||||
"""Mock cloud is logged in."""
|
"""Mock cloud is logged in."""
|
||||||
|
|
232
tests/components/cloud/test_repairs.py
Normal file
232
tests/components/cloud/test_repairs.py
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
"""Test cloud repairs."""
|
||||||
|
from collections.abc import Awaitable, Callable, Generator
|
||||||
|
from datetime import timedelta
|
||||||
|
from http import HTTPStatus
|
||||||
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
|
||||||
|
from homeassistant.components.cloud import DOMAIN
|
||||||
|
import homeassistant.components.cloud.repairs as cloud_repairs
|
||||||
|
from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
import homeassistant.helpers.issue_registry as ir
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
from homeassistant.util import dt
|
||||||
|
|
||||||
|
from . import mock_cloud
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
|
|
||||||
|
async def test_do_not_create_repair_issues_at_startup_if_not_logged_in(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
):
|
||||||
|
"""Test that we create repair issue at startup if we are logged in."""
|
||||||
|
issue_registry: ir.IssueRegistry = ir.async_get(hass)
|
||||||
|
|
||||||
|
with patch("homeassistant.components.cloud.Cloud.is_logged_in", False):
|
||||||
|
await mock_cloud(hass)
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, dt.utcnow() + timedelta(hours=1))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert not issue_registry.async_get_issue(
|
||||||
|
domain="cloud", issue_id="legacy_subscription"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_repair_issues_at_startup_if_logged_in(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
mock_auth: Generator[None, AsyncMock, None],
|
||||||
|
):
|
||||||
|
"""Test that we create repair issue at startup if we are logged in."""
|
||||||
|
issue_registry: ir.IssueRegistry = ir.async_get(hass)
|
||||||
|
aioclient_mock.get(
|
||||||
|
"https://accounts.nabucasa.com/payments/subscription_info",
|
||||||
|
json={"provider": "legacy"},
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("homeassistant.components.cloud.Cloud.is_logged_in", True):
|
||||||
|
await mock_cloud(hass)
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, dt.utcnow() + timedelta(hours=1))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert issue_registry.async_get_issue(
|
||||||
|
domain="cloud", issue_id="legacy_subscription"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_legacy_subscription_delete_issue_if_no_longer_legacy(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
):
|
||||||
|
"""Test that we delete the legacy subscription issue if no longer legacy."""
|
||||||
|
issue_registry: ir.IssueRegistry = ir.async_get(hass)
|
||||||
|
cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": "legacy"})
|
||||||
|
assert issue_registry.async_get_issue(
|
||||||
|
domain="cloud", issue_id="legacy_subscription"
|
||||||
|
)
|
||||||
|
|
||||||
|
cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": None})
|
||||||
|
assert not issue_registry.async_get_issue(
|
||||||
|
domain="cloud", issue_id="legacy_subscription"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_legacy_subscription_repair_flow(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
mock_auth: Generator[None, AsyncMock, None],
|
||||||
|
hass_client: Callable[..., Awaitable[ClientSession]],
|
||||||
|
):
|
||||||
|
"""Test desired flow of the fix flow for legacy subscription."""
|
||||||
|
issue_registry: ir.IssueRegistry = ir.async_get(hass)
|
||||||
|
aioclient_mock.get(
|
||||||
|
"https://accounts.nabucasa.com/payments/subscription_info",
|
||||||
|
json={"provider": None},
|
||||||
|
)
|
||||||
|
|
||||||
|
cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": "legacy"})
|
||||||
|
repair_issue = issue_registry.async_get_issue(
|
||||||
|
domain="cloud", issue_id="legacy_subscription"
|
||||||
|
)
|
||||||
|
assert repair_issue
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}})
|
||||||
|
await mock_cloud(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_start()
|
||||||
|
|
||||||
|
client = await hass_client()
|
||||||
|
|
||||||
|
resp = await client.post(
|
||||||
|
"/api/repairs/issues/fix",
|
||||||
|
json={"handler": DOMAIN, "issue_id": repair_issue.issue_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
flow_id = data["flow_id"]
|
||||||
|
assert data == {
|
||||||
|
"type": "form",
|
||||||
|
"flow_id": flow_id,
|
||||||
|
"handler": DOMAIN,
|
||||||
|
"step_id": "confirm_change_plan",
|
||||||
|
"data_schema": [],
|
||||||
|
"errors": None,
|
||||||
|
"description_placeholders": None,
|
||||||
|
"last_step": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
||||||
|
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
flow_id = data["flow_id"]
|
||||||
|
assert data == {
|
||||||
|
"type": "external",
|
||||||
|
"flow_id": flow_id,
|
||||||
|
"handler": DOMAIN,
|
||||||
|
"step_id": "change_plan",
|
||||||
|
"url": "https://account.nabucasa.com/",
|
||||||
|
"description_placeholders": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
||||||
|
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
flow_id = data["flow_id"]
|
||||||
|
assert data == {
|
||||||
|
"version": 1,
|
||||||
|
"type": "create_entry",
|
||||||
|
"flow_id": flow_id,
|
||||||
|
"handler": DOMAIN,
|
||||||
|
"title": "",
|
||||||
|
"description": None,
|
||||||
|
"description_placeholders": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert not issue_registry.async_get_issue(
|
||||||
|
domain="cloud", issue_id="legacy_subscription"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_legacy_subscription_repair_flow_timeout(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_client: Callable[..., Awaitable[ClientSession]],
|
||||||
|
):
|
||||||
|
"""Test timeout flow of the fix flow for legacy subscription."""
|
||||||
|
issue_registry: ir.IssueRegistry = ir.async_get(hass)
|
||||||
|
|
||||||
|
cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": "legacy"})
|
||||||
|
repair_issue = issue_registry.async_get_issue(
|
||||||
|
domain="cloud", issue_id="legacy_subscription"
|
||||||
|
)
|
||||||
|
assert repair_issue
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}})
|
||||||
|
await mock_cloud(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_start()
|
||||||
|
|
||||||
|
client = await hass_client()
|
||||||
|
|
||||||
|
resp = await client.post(
|
||||||
|
"/api/repairs/issues/fix",
|
||||||
|
json={"handler": DOMAIN, "issue_id": repair_issue.issue_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
flow_id = data["flow_id"]
|
||||||
|
assert data == {
|
||||||
|
"type": "form",
|
||||||
|
"flow_id": flow_id,
|
||||||
|
"handler": DOMAIN,
|
||||||
|
"step_id": "confirm_change_plan",
|
||||||
|
"data_schema": [],
|
||||||
|
"errors": None,
|
||||||
|
"description_placeholders": None,
|
||||||
|
"last_step": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch("homeassistant.components.cloud.repairs.MAX_RETRIES", new=0):
|
||||||
|
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
flow_id = data["flow_id"]
|
||||||
|
assert data == {
|
||||||
|
"type": "external",
|
||||||
|
"flow_id": flow_id,
|
||||||
|
"handler": DOMAIN,
|
||||||
|
"step_id": "change_plan",
|
||||||
|
"url": "https://account.nabucasa.com/",
|
||||||
|
"description_placeholders": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
flow_id = data["flow_id"]
|
||||||
|
assert data == {
|
||||||
|
"type": "abort",
|
||||||
|
"flow_id": flow_id,
|
||||||
|
"handler": "cloud",
|
||||||
|
"reason": "operation_took_too_long",
|
||||||
|
"description_placeholders": None,
|
||||||
|
"result": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert issue_registry.async_get_issue(
|
||||||
|
domain="cloud", issue_id="legacy_subscription"
|
||||||
|
)
|
Loading…
Add table
Reference in a new issue